精通 SwiftUI - iOS 17 版

第 13 章
使用選擇器、切換與步進器建立表單

行動 App 運用表單與使用者互動,並從使用者請求所需的資料。每天使用 iPhone 時, 你很可能碰到行動表單。舉例而言,行事曆 App 可能顯示表單,讓你填寫新行程的資訊, 或者購物 App 顯示表單,要求你提供購物與付款資訊。作為一個使用者,我不否認我討厭填寫表單,但是作為開發者,這些表單可幫助我們與使用者互動,並請求資訊來完成某些操作。開發一個表單,絕對是你需要掌握的基本技能。

在 SwiftUI 框架中,有一個名為「Form」的特別UI 控制元件。使用這個新控制元件, 你可以輕鬆建立表單。我將教你如何使用 Form 元件來建立表單。在建立表單時,你也將學習如何使用常見的控制元件,例如:選擇器(picker )、切換(toggle )與步進器(stepper )。

圖 13.1. 建立一個設定畫面
圖 13.1. 建立一個設定畫面

那麼,我們準備要做什麼專案呢?以圖 13.1 為例,我們將為前面章節所製作的餐廳 App 來建立其設定畫面,該畫面提供使用者設定「排序」與「篩選」的偏好選項。這種類型的畫面在真實的專案中很常見,當你了解其如何運作,你將可在你的 App 專案中建立你自己的表單。

在本章中,我將著重在實作表單佈局,你將了解如何使用「Form」元件來佈局設定畫面,我們也將實作選擇器來選擇「排序」的偏好,並建立一個切換與一個步進器,以表示「篩選」的偏好。當你了解如何佈局表單後,在下一章中,我將教你如何依照使用者的偏好來更新清單,以使 App 的功能完善。你將學會如何儲存使用者的偏好、分享視圖間的資料,並以 @EnvironmentObject 來監控資料的更新。

準備起始專案

為了節省你從頭再建一次餐廳清單的時間,我已經建好一個起始專案。首先,至 https://www.appcoda.com/resources/swiftui5/SwiftUIFormStarter.zip下載起始專案,下載之後,以 Xcode 開啟 SwiftUIForm.xcodeproj 檔,在畫布上預覽 ContentView.swift,你將看到熟悉的 UI,除此之外,還加入更多的餐廳細節資訊,如圖 13.2 所示。

圖 13.2. 餐廳清單視圖
圖 13.2. 餐廳清單視圖

現在,Restaurant 結構具有三個屬性:「type」、「phone」與「priceLevel」。我覺得「type」與「phone」的意思本身很清楚了,不必另外說明,而「priceLevel」則是儲存了範圍為 1 至 5 的整數,以反映該餐廳的平均價位。restaurants 陣列已預填一些範本資料,為了之後的測試,將一些範本餐廳的 isFavoriteisCheckIn設定為「true」,這就是為何你會在預覽中看到一些打卡與最愛符號的原因。

建立表單UI

如前所述,SwiftUI 提供一個名為「Form」的 UI 元件來建立表單 UI。輸入資料時,它是一個用於存放及分組控制元件的容器(例如:切換)。與其向你解釋用法,不如我們直接進入實作,在此過程中,你將了解如何使用該元件。

由於我們將建立一個單獨的設定畫面,因此為表單建立一個新檔案。在專案導覽器中,在「SwiftUIList」資料夾按右鍵,並選取「New File....」,如圖 13.3 所示。接下來,選擇使用「SwiftUI View」作為模板,並將檔案命名為「SettingView.swift」。

圖 13.3. 建立一個新 SwiftUI 檔
圖 13.3. 建立一個新 SwiftUI 檔

現在,我們來開始建立表單。以下列程式碼替代 SettingView

struct SettingView: View {
    var body: some View {
        NavigationStack {
            Form {
                Section(header: Text("SORT PREFERENCE")) {
                    Text("Display Order")
                }

                Section(header: Text("FILTER PREFERENCE")) {
                    Text("Filters")
                }
            }

            .navigationBarTitle("Settings")
        }        
    }
}

要佈局表單,你只需要使用 Form 容器,在其中建立所需要的 UI 元件。在上列的程式碼中,我們建立兩個區塊:「Sort Preference」與「Filter Preference」。每個區塊都有一個文字視圖,你的畫布應會顯示如圖 13.4 所示的預覽。

圖 13.4. 建立一個包含兩個區塊的簡易表單
圖 13.4. 建立一個包含兩個區塊的簡易表單

建立選擇器視圖

在顯示表單時,你一定希望保護某些資訊。如果只顯示一個文字元件是無用的。在實際的表單中,我們使用三種類型的 UI 控制元件來進行使用者輸入,包括選擇器視圖、切換與步進器。先從「排序」偏好來開始,我們將實作一個選擇器視圖。

對於「排序」偏好,可讓使用者選擇餐廳清單的顯示順序,其中我們提供三個選項供使用者選擇:

  1. Alphabetically(依字母順序)。
  2. Show Favorite First(最愛優先)。
  3. Show Check-in First(打卡優先)。

Picker 控制元件非常適合處理此類輸入。首先,你如何在程式碼中表示上述的選項呢? 你可能考慮使用一個陣列來存放這些選項。好的,我們在 SettingView 中宣告一個名為 displayOrders 的陣列:

private var displayOrders = [ "Alphabetical", "Show Favorite First", "Show Check-in First"]

要使用選擇器,你還需要宣告一個狀態變數來儲存使用者所選的選項。在 SettingView 中宣告變數如下:

@State private var selectedOrder = 0

這裡的「0」表示 displayOrders 的第一個項目。現在以下列程式碼替代「SORT PREFERENCE」區塊:

Section(header: Text("SORT PREFERENCE")) {
    Picker(selection: $selectedOrder, label: Text("Display order")) {
        ForEach(0 ..< displayOrders.count, id: \.self) {
            Text(self.displayOrders[$0])
        }
    }
}

這是在 SwiftUI 中建立選擇器容器的方式。你必須提供兩個值,包括所選的綁定(即 $selectedOrder)以及描述選項用途的文字標籤。在閉包中,以 Text 的形式顯示可用選項。

在畫布中,你應該會看到「Display Order」(顯示順序)設定為「Alphabetical」,如圖13.5 所示,這是因為selectedOrder 預設為「0」。如果你點選「Play」按鈕來測試視圖, 點擊該選項會將帶你到下一個畫面,其畫面顯示了所有的可用選項,你可以選擇任何一個選項(例如:Show Favorite First )進行測試。當你回到設定畫面,「Display Order」會出現你剛才的選擇,這便是 @State 關鍵字的強大之處,它自動監控變化,並幫助你儲存選擇的狀態。

圖 13.5. 對「Display Order」選項使用選擇器視圖
圖 13.5. 對「Display Order」選項使用選擇器視圖

使用切換開關

接下來,我們進入設定篩選偏好的輸入。首先,我們實作一個「切換」(或開關)來啟用/ 禁用「Show Check-in Only」的篩選。「切換」有兩個狀態:「ON」或「OFF」,這對於提示使用者在兩個互斥選項中選擇特別有用。

使用 SwiftUI 建立一個切換開關非常簡單,與 Picker 類似,我們必須宣告一個狀態變數來儲存「切換」的目前設定。因此,在 SettingView 宣告下列的變數:

@State private var showCheckInOnly = false

接著,更新「FILTER PREFERENCE」區塊如下:

Section(header: Text("FILTER PREFERENCE")) {
    Toggle(isOn: $showCheckInOnly) {
        Text("Show Check-in Only")
    }
}

你使用 Toggle 建立一個切換開關,並傳送「切換」的目前狀態。在閉包中,你顯示切換的描述,這裡我們只使用一個 Text 視圖。

上面是實作切換所需的程式碼,畫布應該會在「Filter Preference」區塊下顯示一個切換開關,如圖13.6 所示。如果你執行這個 App,你可以在 ON 與 OFF 狀態之間切換。同樣的,這個狀態變數 showCheckInOnly 將會持續追蹤使用者的選擇。

圖 13.6. 顯示一個切換開關
圖 13.6. 顯示一個切換開關

使用步進器

設定表單中最後一個 UI 控制元件是「步進器」。再次參考圖 13.1,使用者可以透過設定價位級別來篩選餐廳, 每間餐廳都有一個價位指示器,範圍在1 至 5 之間。使用者可調整價位級別,以縮小清單視圖中顯示的餐廳數量。

在設定表單中,我們將實作一個步進器,供使用者調整設定。基本上,iOS 中的步進器顯示了一個「+」和「-」的按鈕,來執行遞增及遞減的動作。

要在 SwiftUI 中實作步進器,我們首先需要一個狀態變數來存放步進器的目前值。在本例中,這個變數儲存使用者選擇的價位級別。在 SettingView 中宣告狀態變數,如下所示:

@State private var maxPriceLevel = 5

我們預設 maxPriceLevel 為「5」,現在 FILTER PREFERENCE 的區塊更新如下:

Section(header: Text("FILTER PREFERENCE")) {
    Toggle(isOn: $showCheckInOnly) {
        Text("Show Check-in Only")
    }

    Stepper(onIncrement: {
        self.maxPriceLevel += 1

        if self.maxPriceLevel > 5 {
            self.maxPriceLevel = 5
        }
    }, onDecrement: {
        self.maxPriceLevel -= 1

        if self.maxPriceLevel < 1 {
            self.maxPriceLevel = 1
        }
    }) {
        Text("Show \(String(repeating: "$", count: maxPriceLevel)) or below")
    }
}

你可透過初始化一個 Stepper元件來建立一個步進器。對於 onIncrement 參數,你指定點選「+」按鈕時要執行的動作。在程式碼中,我們只將 maxPriceLevel 增加「1」。反之, 點選「-」按鈕時將執行 onDecrement 參數中指定的程式碼。

由於價位級別在 1 至 5 的範圍之間,我們執行檢查來確保 maxPriceLevel的值介於 1 至 5 之間。在閉包中,我們顯示篩選偏好的文字描述。這裡的最高價位是以美元符號表示, 如圖 13.7 所示。

圖 13.7. 實作一個步進器
圖 13.7. 實作一個步進器

要測試步進器,則點選「Play」按鈕來執行 App。當你點選 +/ - 按鈕時,將會調整 $ 符號的數量。

顯示表單

現在你已經完成了表單UI,下一步是顯示表單給使用者。以本範例來說,我們將以強制回應視圖的形式來顯示此表單。在內容視圖中,我們將會在導覽列加入一個「Setting」按鈕,以觸發設定視圖。

切換至 ContentView.swift,我假設你已經閱讀過強制回應視圖一章,因此我將不再深入解釋程式碼。首先,我們需要一個變數來追蹤強制回應視圖狀態(即顯示或不顯示)。插入下列這行程式碼來宣告狀態變數:

@State private var showSettings: Bool = false

接下來,將下列的修飾器插入至 NavigationStack 中(加在navigationTitle之後):

.toolbar {
    ToolbarItem(placement: .navigationBarTrailing) {
        Button(action: {
            self.showSettings = true
        }, label: {
            Image(systemName: "gear").font(.title2)
        })
        .tint(.black)
    }
}
.sheet(isPresented: $showSettings) {
    SettingView()
}

·toolbar 修飾器和ToolbarItem 可讓你在導覽列加入一個按鈕,你可在導覽列前緣(navigationBarLeading) 或後緣(navigationBarTrailing )位置建立一個按鈕。由於我們想在右上角顯示按鈕,因此我們使用 navigationBarTrailing 參數。sheet修飾器用於以強制回應視圖的形式顯示 SettingView

在畫布中,你應該會在導覽列中看到一個齒輪圖示,如圖 13.8 所示。如果你執行 App,並點選齒輪圖示,即會帶出設定視圖。

圖 13.8. 建立一個導覽列按鈕
圖 13.8. 建立一個導覽列按鈕

作業

現在解除設定視圖的唯一方式是使用向下滑動手勢。在第 12 章中,你已經學過如何以程式設計方式來解除強制回應視圖。我們來練習修改,請在導覽列建立「Save」與「Cancel」按鈕,如圖13.9 所示。你不需要實作這些按鈕,當使用者點擊任一按鈕時,你只需解除設定視圖即可。

圖 13.9. 在導覽列建立「Save」與「Cancel」按鈕
圖 13.9. 在導覽列建立「Save」與「Cancel」按鈕

接下來的任務

我希望你了解 Form 元件,並知道如何使用選擇器與步進器等元件來建立一個表單 UI。至目前為止,這個 App 無法永久儲存使用者偏好。每次啟動 App 後,設定都會重置回原始設定。在下一章中,我將教你如何在本地儲存器中儲存這些設定。更重要的是,我們將會依照使用者偏好來更新清單視圖。

在本章所準備的範例檔中,有完整的專案可供下載: