上一章都在是講解新增商品頁面的 Layout,本章會談論到新增商品的功能。我們會談論到提示窗 (Alert) 和導覽列 (Navigation Bar) 的使用。另外,我們還會介紹另一個 Property Wrapper 叫 @Environment
。
SwiftUI Coffee Shop
新增商品功能
下面的程式碼中,我們省略了一些部分,並新增我們這章要教學的。
struct AddProductView: View { @State var name: String = "" @State var price: String = "" @State var isFailed = false private var onAddAction: ((Item) -> Void)? var body: some View { VStack { ... Button(action: { let isSuccessful = self.addProduct() self.isFailed = !isSuccessful }, label: { Text("Add") }) ... .alert(isPresented: $isFailed) { Alert(title: Text("Input data is incorrect"), dismissButton: .default(Text("OK"))) } ... } } private func addProduct() -> Bool { guard !name.isEmpty else { return false } guard let parsedPrice = Double(price) else { return false } let item = Item(name: name, price: parsedPrice) onAddAction?(item) return true } } extension AddProductView { func onAdd(perform action: ((Item) -> Void)?) -> Self { var copy = self copy.onAddAction = action return copy } }
可以看到 Button
被按下後,馬上就呼叫 addProduct()
。它會檢查欄位是否有都有輸入值。沒有的話則回傳 false
,有的話則新建 Item。然後,呼叫 onAddAction()
回調 (Callback),並回傳 true
。
onAddAction
Callback 會是由 ProductListView
呼叫 onAdd()
傳過來的。在 onAdd()
中,有先對自己做個備份,再將 action
設給 onAddAction
。為何不直接設定呢?而是要先拷貝再設定呢?因為 struct
內的函式是不可以改變自己內部的 Property。將 self
設給 copy
後,copy
和 self
就是不同的實體。在 Swift 中,struct
是傳值而不是傳址。
Alert
isFailed
被宣告為 State。當 addProduct()
回傳 false
,就會把 isFailed
設為 true,.alert()
就會被觸發。
Alert 會顯示提示窗。參數 title:
是提示窗的標題,而參數 dismissButton:
是按鈕上的字。所以 Alert 會顯示輸入資料不正確。
導覽列 (Navigation Bar)
本章至目前為止我們都還單獨在 AddProductView
上開發。現在我們來將 ProductListView
和 AddProductView
串連起來。讓我們來為 Coffee Shop App 加上導覽列 (Navigation Bar) 的功能吧。
在 SwiftUI 裡加上 Navigation Bar 是非常簡單。我們回到最上層的 View,也就是 ContentView
。在 ContentView
中,我們只要用 NavigationView
將 ProductListView
包起來就可以了。因為是加在最上層,所以現在整個 App 都有 Navigation Bar 的功能。
struct ContentView: View { var body: some View { NavigationView { ProductListView() } } }
既然已經為 App 加上 Navigation Bar 的功能。那我們來為 ProductListView
加上導覽列標題 (Navigation Bar Title)。在 ProductListView
裡面的最外層的 List
上,我們呼叫 .navigationBarTitle()
就可以了。
在 Navigation Bar 的左右兩邊,呼叫 .navigationBarItems()
可以加上按鈕。trailing:
表示是要加在右邊。按鈕上的 UI 是 Image(systemName: "plus")
,這是個+號。iOS 有提供一些系統預設的 Image。用這些 Image 的名稱就可以取用。可以在這邊找到這些預先安裝的 Image 列表。
然後我們希望點擊這個+號後,可以導覽到 AddProductView
。要實現這功能,我們只要將這個+號用 NavigationLink
包起來即可。參數 destination:
是指要導覽的目的地,所以這裡填入 AddProductView
。
還記得上面我們有替 AddProductView
加上 onAdd()
Callback 嗎?這是當新增商品時,AddProductView
將新增的商品,透過 onAdd()
傳回來給 ProductListView
。然後 ProductListView
就可以將新增的商品加入到商品列表中。
struct ProductListView: View { var body: some View { List { ... } .navigationBarTitle("Product List") .navigationBarItems(trailing: NavigationLink(destination: AddProductView().onAdd(perform: onAdd)) { Image(systemName: "plus") }) } private func onAdd(_ item: Item) { products.append(item) } }
現在你可以從 ProductListView
導覽到 AddProductView
。但是還沒有辦法返回。讓我們回到 AddProductView
。
首先我們先為 AddProductView
加上 Navigation Bar Title。
Property presentationMode
被宣告為 Environment。@Environment
是讓我們可以存取一些預先定義好的 View’s Environment 變數。\.presentationMode
就是其中一個預先定義好的 Key。我們只要在 Button
的動作裡面,呼叫 presentationMode.dismiss()
就可以導覽回上一頁。
struct AddProductView: View { @Environment(\.presentationMode) var presentationMode ... var body: some View { VStack { ... Button(action: { let isSuccessful = self.addProduct() self.isFailed = !isSuccessful if isSuccessful { self.presentationMode.wrappedValue.dismiss() } }, label: { Text("Add") }) ... ... } .navigationBarTitle("Add a product") } }
新增商品功能終於完成了!
結語
本章節介紹了 Navigation Bar 的使用。SwiftUI 使得 Navigation Bar 非常易於使用。至目前為止,我們已經介紹了很多 UI 元件的使用,而且它們的使用方式都非常的相似。如果你有熟悉這些使用方式的話,之後當你遇到新的 UI 時,一定會可以很容易的就上手。