我們把新增商品的頁面分為兩個章節。第一章我們會介紹 VStack
、Button
和 TextField
等 UI 元件,另外還會介紹 @State
。在上一章已經學了一些 UI 元件,所以這章的 UI 元件部分會容易多。SwiftUI 的 @State
是全新概念,務必要多花一點時間了解。
SwiftUI Coffee Shop
新增商品頁
Coffee Shop 的新增商品頁面如下圖。有商品名稱和價格的輸入框,和一個新增按鈕。
在 Product 資料夾下新增一個 SwiftUI 檔案叫 AddProductView.swift。貼上以下的程式碼。
struct AddProductView: View { @State var name: String = "" @State var price: String = "" var body: some View { VStack { HStack { Text("Product name:") TextField("Input product name", text: $name) .textFieldStyle(RoundedBorderTextFieldStyle()) // 設定輸入框的邊框為圓角樣式 } .padding() HStack { Text("Price:") TextField("Input price", text: $price) .textFieldStyle(RoundedBorderTextFieldStyle()) .keyboardType(.decimalPad) // 設定輸入框的鍵盤為數字鍵盤 } .padding() Button(action: { print("on add clicked") }, label: { Text("Add") }) .padding() .frame(maxWidth: .infinity) .background(Color("ButtonColor")) // 背景顏色 .foregroundColor(Color.white) // 前景顏色,也就是字的顏色 .cornerRadius(30) // round border 為 30 .font(.headline) // 字體樣式 .padding() Spacer() } } } struct AddProductView_Previews: PreviewProvider { static var previews: some View { AddProductView() } }
整個版面是一列一列地垂直排列下去,所以我們用 VStack
做最外層的 Layout 管理容器。HStack
和 Label
已經在商品列表中有講解過,所以我們這裡不在重述。
TextField
是輸入欄位。第一個參數是 Placeholder,就是提示字串。使用者輸入後的字串,會放到第二參數的變數中。詳細的語法講解等等下面會說明。
HStack
後面有接一個 .padding()
,讓 HStack
和四周有些空間。這樣比較不會擁擠。
Button
是按鈕。第一個參數是按鈕被按下後,會觸發的動作。目前我們只是印出一個字串代表有接收到事件 (Event),詳細的實作會在下個章節。第二個參數是按鈕的 UI 顯示。
Botton 下面有很多 UI 相關的設定,其中 .frame(maxWidth: .infinity)
是要讓 Button
的寬度可以撐到最大。
最後面有個 Spacer
。VStack
預設是將它的 Child Views 置中 (Align Center)。但是我們希望表格是置頂 (Align Top)。所以在最後面加上一個 Spacer
,他會將高度撐到最大,把上面的 Child Views 推到頂部。
Color Set
在 Botton
下面有 .background(Color("ButtonColor"))
,這是背景顏色設定。Color
中有很多已經預先定義好的顏色,如 .foregroundColor(Color.white)
就是直接使用預先定義好的白色來設定前景。但是我們要的顏色並沒有被預先定義。
Xcode 提供一個方式讓我們可以自定義顏色,就是 Color Set。點選 Assets.xcassets -> New Color Set。
因為程式中是 Color(“ButtonColor”),所以將 Color Set 命名為 ButtonColor。然後依造下圖將顏色設定為 #D06976。然後在程式中就可以用 Color(“ButtonColor”) 來取得 ButtonColor 對應的顏色。
如果你仔細看一下 Color
,會發現它有很多建構子。其中有可以直接傳入 RGB 色碼,這樣就不需要建立 Color Set。但是這麼做的話,色碼會散落在程式中。同一個色碼可能會在程式中多次被使用。當你要一起改色碼的話,那就很令人頭痛了。
@State
在 AddProductView
中有兩個 Property,分別是 name
和 price
。它們前面有 @State
。符號 @ 表示 State 是一個 Property Wrapper。Property Wrapper 是 Swift 5 之後才有的新語法。
當一個 Property 宣告為 @State
時,我們說這個 Property 是個 State,而 SwiftUI 會管理這個 Property 的值。當 State 被改變時,整個 AddProductView
就會被 Invalidate (設為無效),也就導致 AddProductView
會更新並重畫。
為何 SwiftUI 可以偵測到 State 的值被改變呢?因為當 Property 被宣告為 State 時,State 會對這個 Property 做一個包裝 (Wrap)。所以程式不是直接改變 Property 的值,而是透過 State 來改變 Property 的值。這也是為何 @State
是個 Property Wrapper。
當設定一個字串給 AddProductView.name
時,不是直接設定給它。而是透過 State 包裝的函式來設定 name
的值,並在這函式中告訴 name
所屬的 View 要更新。而 Swift 將這些包裝細節都隱藏起來了。
當你要把 State 當參數傳給另外的 View 時,就要用 $ 當前綴,像 $name
。符號 $ 叫做 Projected View,也是 Swift 5 的新東西。
這樣你就了解程式中 TextField
的第二個參數,我們傳 $name
而不是 name
。當你在 TextField
中輸入字串,TextField
就會直接將字串放到 $name
。然後 View 就會更新。
@State
和 $ 常像這樣一起搭配使用。用起來的感覺像是網頁開發中的雙向綁定 (two-way binding)。更詳細的解說,可以參考下面這篇文章。
結語
SwiftUI 的 @State
讓我們開發 UI 時更加地方便。當 State 值改變時,View 會自動更新。如果有開過 UIKit 的話,就可以了解它所帶來的方便。Color Set 可以讓我們集中管理整個 App 的色碼。尤其是大型的 App 有上萬行的程式碼時,想改變色碼都不是件簡單的事情。