本章我們會為 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。









