
I think that's the single best piece of advice: Constantly think about how you could be doing things better and questioning yourself.
- Elon Musk, Tesla Motors
在上一章中,我們使用 List 建立了一個簡單的 App 來顯示餐廳清單,而在本章中,我們將會自訂清單視圖來讓它看起來更加時尚,如圖 7.1 所示。而且,從本章開始,你將會開發一個名為「FoodPin」的真實世界 App,這會很有趣 !

首先啟動你的 Xcode, 並使用「App」模板來建立一個新專案, 將專案命名為「FoodPin」,並填寫 Xcode 專案所需的所有選項,這和你在上一章中所做的一樣,如圖 7.2 所示。
Note: 我們正要在建立一個真正的 App,因此我們來取一個更好的名稱。如果你有需要,你可以自由使用其他的名稱。另外,請確保使用自己的組織識別碼,否則你無法在你的 iPhone 實機上測試你的 App。

建立 Xcode 專案後,你應該會看到由 Xcode 產生的 ContentView.swift 檔,其檔名不太適合我們的 FoodPin App,因此在 ContentView.swift 中的「ContentView」上按右鍵,並選擇「Refactor → Rename...」,如圖 7.3 所示。

將新名稱設定為「RestaurantListView」,此重構功能會重新命名檔案以及所有相關的程式碼,如圖 7.4 所示。

Note: 在專案導覽器中選擇「FoodPin」專案,然後點選 Targets 下的「FoodPin」,你應該會看到最低部署目標設定為「iOS 16.0」,這是預設的設定。之後,如果你的 App 需要支援舊版的 iOS 裝置,你可以在這裡進行更改,而本書這裡將原封不動保留設定。

由於我們將提供包含餐廳名稱與圖片的餐廳清單,因此先到下列網址下載圖片包: http://www.appcoda.com/resources/swift53/simpletable-images2.zip ,並將所有的圖片匯入素材目錄中,如圖 7.6 所示。

現在我們準備建立資料陣列, 並在餐廳清單視圖中顯示項目。和之前一樣, 在 RestaurantListView 結構中宣告restaurantNames 陣列:
var restaurantNames = ["Cafe Deadend", "Homei", "Teakha", "Cafe Loisl", "Petite Oyster", "For Kee Restaurant", "Po's Atelier", "Bourke Street Bakery", "Haigh's Chocolate", "Palomino Espresso", "Upstate", "Traif", "Graham Avenue Meats", "Waffle & Wolf", "Five Leaves", "Cafe Lore", "Confessional", "Barrafina", "Donostia", "Royal Oak", "CASK Pub and Kitchen"]
restaurantNames 陣列存放餐廳名稱的集合。為了要顯示清單,我們可以更新 body 變數如下:
var body: some View {
List {
ForEach(restaurantNames.indices, id: \.self) { index in
Text(restaurantNames[index])
}
}
.listStyle(.plain)
}
我們迴圈遍歷 restaurantNames 陣列中的項目,並在清單中顯示餐廳名稱,而清單樣式設定為「Plain」,如圖 7.7 所示。

你已經完成上一章的作業嗎?我希望你已經努力完成了。在本節中,我們將修改目前的 App 來顯示不同的餐廳圖片。
首先,我們宣告一個名為「restaurantImages」的新陣列,以存放餐廳圖片的檔名。在 RestaurantListView 結構中插入下列的程式碼:
var restaurantImages = ["cafedeadend", "homei", "teakha", "cafeloisl", "petiteoyster", "forkee", "posatelier", "bourkestreetbakery", "haigh", "palomino", "upstate", "traif", "graham", "waffleandwolf", "fiveleaves", "cafelore", "confessional", "barrafina", "donostia", "royaloak", "cask"]
請注意,圖片的順序是與 restaurantNames 的順序一致。要在餐廳名稱旁邊顯示圖片, 則更新 List 視圖內的程式碼,如下所示:
List {
ForEach(restaurantNames.indices, id: \.self) { index in
HStack {
Image(restaurantImages[index])
.resizable()
.frame(width: 40, height: 40)
Text(restaurantNames[index])
}
}
}
.listStyle(.plain)
我們在上一章中討論過程式碼,但我們沒有為 Image 指定一個固定的圖片名稱,而是將圖片名稱設定為「restaurantImages[index]」,這就是我們如何顯示餐廳相應圖片的方式。

列佈局非常簡單,我們將透過重新設計佈局,使其變得更好。以下是我們預計要做的修改:讓餐廳圖片大一點,以及顯示關於餐廳的更多資訊(例如:位置與類型),最重要的是我們會將圖片改為圓角。為了讓你對於如何重新設計列佈局更加了解,請看一下圖 7.9,它看起來棒極了,對吧?而且,我忘了提到新佈局在深色模式下看起來也很棒。

為了建立列佈局,我們將同時使用 HStack 與 VStack。現在更新 ForEach 中的程式碼如下:
HStack {
Image(restaurantImages[index])
.resizable()
.frame(width: 120, height: 118)
VStack(alignment: .leading) {
Text(restaurantNames[index])
.font(.system(.title2, design: .rounded))
Text("Type")
.font(.system(.body, design: .rounded))
Text("Location")
.font(.system(.subheadline, design: .rounded))
.foregroundStyle(.gray)
}
}
VStack 視圖是用於排列餐廳名稱、類型與位置。Image 視圖與 VStack 視圖都要嵌入在水平堆疊視圖中,以建立所需的佈局,如圖 7.10 所示。

稍等 !列佈局與圖 7.9 所示的並不完全相同。為了解決這些差異,我們需要將 VStack 與 HStack 的頂部對齊,並加入一些間距。你可以透過更新 HStack 並調整一些參數來輕鬆修正這些問題,如下所示:
HStack(alignment: .top, spacing: 20) {
.
.
.
}
當你變更完成後,應該可修正對齊問題,如圖 7.11 所示。

這裡還有一個問題是我們需要對圖片進行圓角處理。使用 SwiftUI,你可以透過將 clipShape 修飾器加到 Image 視圖來輕鬆建立圓角。
Image(restaurantImages[index])
.resizable()
.frame(width: 120, height: 118)
.clipShape(RoundedRectangle(cornerRadius: 20))
我們已將剪裁形狀(clipping shape )設定為圓角半徑為 20 點的「RoundedRectangle」, 此修飾器可讓你將視圖剪裁為特定的形狀。在上列的程式碼中,我們告訴修飾器將圖片遮罩為圓角矩形,從而產生圓角圖片。如果你想要更尖銳的角,則可將該值修改為你偏好的較低值,如圖 7.12 所示。

RoundedRectangle 只是內建形狀之一,你可以應用其他的形狀,例如:Circle() 與 Capsule()。
預設上,List 視圖使用行分隔符號來分隔每一列,如果你想要隱藏分隔符號,SwiftUI 提供一個名為「listRowSeparator」的修飾器,來控制行分隔符號的可見性。
在List 視圖中,你可以加入 listRowSeparator 修飾器,並將其值設定為「.hidden」,以隱藏分隔符號,如圖 7.13 所示。

自 iOS 13 發布以來,Apple 讓使用者在淺色和深色的全系統外觀之間進行選擇。當使用者選擇採用深色模式時,系統與App 會對所有的畫面及視圖使用深色的調色板。作為一個 App 開發者,你應該確保你的 App 能夠遵從深色模式,有多種方式可在深色模式下測試你的 App。
首先,在 RestaurantListView.swift 中插入另一個 #Preview 程式碼區塊如下:
#Preview("Dark mode") {
RestaurantListView()
.preferredColorScheme(.dark)
}
preferredColorScheme 修飾器讓你透過傳送 .dark 值來切換到深色模式。透過此更新,現在你可以在預覽窗格中以淺色與深色模式預覽 App UI,參考一下圖 7.14,即使不做任何修改,App UI 在深色模式下看起來也很棒。另外,你可以使用預覽畫布中內的「Variants」選項。透過選擇「Color Scheme Variants」,Xcode 將為你提供淺色與深色模式的預覽。

第三種在深色模式下預覽App 的方式是使用模擬器。在模擬器上執行 App 時,你可以點選「Environment Overrides」按鈕來將外觀從淺色切換到深色。當你啟用這個開關後,模擬器將設定為採用深色模式,如圖 7.15 所示。

最後一種測試深色模式的方式是調整模擬器的設定。在模擬器上,點選「Settings → Developer」,將「Dark Appearance」選項的開關切換為「ON」,以啟用深色模式。
目前,App 顯示所有列的位置(Location )與類型(Type )。作為練習,我將讓你自行解決這個問題,你可以編輯原始碼來更新位置與類型標籤。以下是你進行這個作業所需要的兩個陣列:
var restaurantLocations = ["Hong Kong", "Hong Kong", "Hong Kong", "Hong Kong", "Hong Kong", "Hong Kong", "Hong Kong", "Sydney", "Sydney", "Sydney", "New York", "New York", "New York", "New York", "New York", "New York", "New York", "London", "London", "London", "London"]
var restaurantTypes = ["Coffee & Tea Shop", "Cafe", "Tea House", "Austrian / Causual Drink", "French", "Bakery", "Bakery", "Chocolate", "Cafe", "American / Seafood", "American", "American", "Breakfast & Brunch", "Coffee & Tea", "Coffee & Tea", "Latin American", "Spanish", "Spanish", "Spanish", "British", "Thai"]
上一個作業對你來說也許太簡單了,這裡有另一個挑戰,試著重新設計列佈局,看看是否可以建立出如圖 7.16 所示的 App。

恭喜 !你已經取得了顯著的進步。一旦你完全理解清單視圖,你就具備了建立出色 UI 的能力。「清單視圖」是大多數 iOS App 的基礎,除非你正在開發遊戲,否則你在建立自己的 App 時,很可能需要以某種方式實作清單視圖,因此我鼓勵你花一些時間進行練習, 並編寫程式碼。請記住,「從做中學」是學習編寫程式碼的最有效率的方式。
在本章所準備的範例檔中,有最後完整的Xcode 專案 http://www.appcoda.com/resources/swift59/swiftui-foodpin-custom-list.zip 。與作業的解答 http://www.appcoda.com/resources/swift59/swiftui-foodpin-custom-list-exercise.zip 供下載。