本章我們會為 Coffee Shop App 加上商品列表。在這頁面中,你會學習到如何使用 SwiftUI 中的 List
、HStack
、Text
和 Spacer
等 UI 元件。此外,還會介紹 ForEach
和 Identity Key Path (\.field)。還沒有接觸過 SwiftUI 的讀者,本章可能會有點吃力。請多花一點時間消化一下內容。
SwiftUI Coffee Shop
新增商品資料 – Item
在 CoffeeShop 資料夾下面新增一個 Product 資料夾。之後有關商品的程式碼都會放在這個資料夾下面。
在 Product 下新增 Item.swift。Item
代表一個商品,商品有名稱 (name
) 和 價格 (price
)。
// Item.swift struct Item { let name: String? let price: Double }
你在 Coffee Shop 完整的程式碼中是找不到 Item
。因為我們之後會用 Product
來取代它。之所以目前會取名為 Item
,是因為怕之後會搞混。
新增商品列 – Row
在 Product 資料夾下新增 ProductRowView.swift。請直接選擇 SwiftUI View 範本來建立。
我們想要在列表中的每一列上顯示一個商品。而每一列的最左邊顯示商品名稱,最右邊顯示商品價格。最後商品列就會看起來如下圖:
HStack
是一個容器 (Container)。它可以自動將裡面的 Child Views 做水平排列。HStack
的 H 指的就是水平 (Horizontal)。相對應的,VStack
就會做垂直排列。
Text
可用來顯示文字。所以我們需要兩個來顯示名稱和價格。
HStack
會自動將 Child Views 依序向左靠齊排列。可是我們希望名稱是靠左,而價格是靠右。所以我們這兩個中間插入 Spacer
。Spacer
的寬度會自動撐到最大。所以就可以將名稱和價格擠到兩邊。
最後 ProductRowView
的程式碼就會如下。它會接收一個 Item,並將 Item 顯示出來。其中注意到最後一行程式碼,我們將 HStack
的寬度撐到無限大,也就是 Row 本身的寬度會是無限大。到時候放到 List
裡面,就會撐滿 List
的寬度。
struct ProductRowView: View { let product: Item var body: some View { HStack { Text(product.name ?? "") .font(.custom("Georgia", size: 20)) // 將字體設為 Georgia, 大小為 20 Spacer() Text(String(format: "$ %.2f", product.price)) // 價格顯示至小數後兩位 .font(.custom("Georgia", size: 20)) } .frame(height: 44) // HStack 的高度設為 44 .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading) // HStack 的寬度為無限大 } }
在 ProductRowView.swift 最下方還可以看到 ProductRowView_Previews
。將它替換成下方的程式碼。
struct ProductRowView_Previews: PreviewProvider { static var previews: some View { ProductRowView(product: Item(name: "Americano", price: 25)) } }
然後點擊右上方的 Resume。
你就可以看到 ProductRowView_Previews
的預覽。也就是說,我們可以藉由 ProductRowView_Previews
來預覽 ProductRowView
目前是長怎麼樣。我非常喜歡這個功能!
新增商品列表 – List
在 Product 資料夾下新增 ProductListView.swift。
我們會將每一個商品顯示在列表上。因為我們還沒有新增商品的功能,所以先在程式碼裡定義兩個商品。
對於每一個 Child View,我們要提供一個唯一識別碼給 List
。我們用 Item.name
作為唯一識別碼。程式碼中的斜線 (\) 是 Identity Key Path,這是 Swift 新的語法。所以 \.name
就是告訴 List
使用 Item.name
作為 id。
ForEach
會輪詢 products
陣列裡面的每個 Item
。然後,我們在 closure 裡面對每個 Item
回傳 ProductRowView
。之後 List
會依據 ForEach
來畫出每一列。
struct ProductListView: View { let products = [ Item(name: "Americano", price: 2.5), Item(name: "Espresso", price: 3), ] var body: some View { List { ForEach(products, id: \.name) { item in ProductRowView(product: item) } } } }
最後預覽一下我們剛剛的商品列表程式。
另外,List
也可以不需要 ForEach
,可以直接對 products
陣列做輪詢。
struct ProductListView: View { let products = [ Item(name: "Americano", price: 2.5), Item(name: "Espresso", price: 3), ] var body: some View { List(products, id: \.name) { item in ProductRowView(product: item) } } }
整合
商品列表 ProductListView
和商品列 ProductRowView
都開發完成。但是我們剛剛都只是藉由預覽來確認頁面是否設計正確。我們都還沒有執行過程式啊!
在 CoffeeShop 資料夾下的 ContentView
,是專案建立時就一起創建好的。在另外一個檔案 SceneDelegate
裡,會預設用 ContentView
來做第一個頁面。有興趣的話可以看一下 。
在 ContentView
裡面放入 ProductListView
。終於大功告成!跑一下程式看看結果吧!
struct ContentView: View { var body: some View { ProductListView() } }
結語
本章我們終於踏入 SwiftUI 程式設計。文中介紹了 4 種 SwiftUI 元件,分別是 List、HStack、Text 和 Spacer。還有 SwiftUI 的預覽功能,以及 Swift 的 Identity Key Path。如果你有開發過 UIKit 的經驗的話,會感覺到 SwiftUI 和 UIKit 是截然不同的開發方式。此外,Xcode 提供的預覽功能非常地方便。讓你不需要先把程式都寫好,才能看你的畫面有沒有畫正確。而是可以直接預覽你目前正在開發的 View。