
That's been one of my mantras - Focus and Simplicity. Simple can be harder than complex: You have to work hard to get your thinking clean to make it simple. But it's worth it in the end because once you get there, you can move mountains.
- Steve Jobs
現在你已經對範例 App 的原型有了基本概念,本章將進行一些更有趣的內容,並使用清單視圖( List View )建立一個簡單的 App,一旦你能掌握這個技術與清單視圖,我們便會開始建立 FoodPin App。
首先,iPhone App 的清單視圖是什麼呢?如果你之前使用過 UIKit,SwiftUI 中的清單視圖與 UIKit 的表格視圖(Table View )是同樣的東西。清單視圖是 iOS App 中最常見的 UI,大部分的 App(除了遊戲以外)或多或少會使用清單視圖來顯示內容,最常見的便是內建的電話 App,你的聯絡人是以清單視圖來顯示;另一個例子是郵件 App,它利用一個清單視圖來顯示郵件信箱與郵件。不僅是文字資料清單,清單視圖也可以顯示圖片資料,例如: TED、Google+ 以及 Airbnb 皆是不錯的 App 案例。圖 6.1 展示了一些清單式 App 範例,雖然外表看起來有些出入,但這些全部都是使用清單視圖完成的。

我們準備在本章建立一個非常簡單的清單視圖,並學習如何填入資料(圖片與文字)。如果你使用UIKit 實作過表格視圖,應該知道實作一個簡單的表格視圖需要花一點工夫。SwiftUI 簡化了整個過程,只需要幾行程式碼,就能以表格形式來顯示清單資料,即使你需要自訂列的佈局,也只需要極少的工夫便可辦到。
仍是覺得困惑嗎?待會你就會明白我的意思。
不要只是閱讀本書。當你想認真學習 iOS 程式語言,則要停止只是閱讀,請開啟你的 Xcode,然後撰寫程式碼,這是學習程式的最佳捷徑。
我們來開始建立一個簡單的 App 吧 !這個 App 非常簡單,我們將在簡單的清單視圖中顯示一串餐廳名稱,下一章中我們將會繼續改造它。若你尚未開啟 Xcode,則開啟它,使用 iOS 下的「App」模板來新建一個專案,如圖 6.2 所示。

點選「Next」按鈕,在Xcode 專案選項中填入下列資訊:
點選「Next」按鈕,接著Xcode 會詢問你要將 SimpleTable 專案儲存在哪裡,在你的 Mac 電腦中挑選一個資料夾,並點選「Create」按鈕來繼續。
建立專案後,Xcode 應該會顯示 ContentView.swift 的內容。從模擬器清單中選取「iPhone 15 Pro」,我建議使用此裝置來預覽 UI。
我們從一個簡單的清單來開始了解 List 視圖的用法。將 ContentView 結構內的程式碼替換為下列的程式碼:
struct ContentView: View {
var body: some View {
List {
Text("Item 1")
Text("Item 2")
Text("Item 3")
Text("Item 4")
}
}
}
以上是建立一個簡單的清單或表格所需要的程式碼。當你將文字視圖嵌入 List 時,清單視圖會以列的形式顯示資料,這裡每一列顯示不同敘述的文字視圖,如圖 6.3 所示。

相同的程式碼片段可以使用 ForEach 來編寫,如下所示:
struct ContentView: View {
var body: some View {
List {
ForEach(1...4, id: \.self) { index in
Text("Item \(index)")
}
}
}
}
由於這些文字視圖非常相似,因此你可在 SwiftUI 中使用 ForEach 迴圈來建立視圖。
從已識別的底層集合中,依照需求計算視圖的一種結構。.
- Apple 官方文件 (https://developer.apple.com/documentation/swiftui/foreach)
你可以提供 ForEach 一組資料集合或一個範圍,不過你必須要注意的是,你需要告訴 ForEach 如何識別集合中的每個項目,參數 id 的目的即在此。而為什麼 ForEach 需要唯一識別項目呢?SwiftUI 功能強大,當集合中的部分或全部項目變更時,它可以自動更新 UI。為了實現這一點,它需要一個識別碼來在更新或刪除項目時唯一識別該項目。
在上列的程式碼中,我們向 ForEach 傳送一個要迴圈遍歷的值範圍。該識別碼設定為其值(即1、2、3、4),index 參數儲存迴圈的目前值,例如:它從「1」開始,index 參數的值則為「1」。
在閉包(ForEach 內的程式碼區塊)中,即是渲染視圖所需的程式碼。這裡我們建立文字視圖,其敘述將會依照迴圈中的 index 值而變化,這就是你如何在清單中建立四個不同標題的項目的方法。
我再教你一種技巧,相同的程式碼片段也可以進一步重寫如下:
struct ContentView: View {
var body: some View {
List {
ForEach(1...4, id: \.self) {
Text("Item \($0)")
}
}
}
}
你可以省略 index 參數,並使用參數名稱縮寫「$0」,它引用閉包的第一個參數。
我們將進一步將程式碼重寫得更簡單些,你可以將資料集合直接傳送到 List 視圖,程式碼如下:
struct ContentView: View {
var body: some View {
List(1...4, id: \.self) {
Text("Item \($0)")
}
}
}
如你所見,只需要幾行程式碼,即可建立一個簡單的清單或表格。
現在你已經知道如何建立一個簡單的清單,接著我們來看如何使用更多樣化的佈局, 參見圖 6.4。在大多數的情況下,清單視圖的項目皆會包含文字與圖片,而你該如何實作呢?如果你知道 Image、Text、VStack 與 HStack 工作原理的話,你應該對如何建立一個複雜的清單有概念了。

現在開啟 ContentView.swift 來編寫 UI 的程式碼。我們宣告 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 And Deli", "Waffle & Wolf", "Five Leaves", "Cafe Lore", "Confessional", "Barrafina", "Donostia", "Royal Oak", "CASK Pub and Kitchen"]
在這個範例中,我們使用陣列來儲存清單資料,如果你忘記陣列的語法,則請參考第 2 章的內容說明。陣列中不同的值是以逗號分隔,然後用一對方括號包裹起來。
當我說:「在結構中插入程式碼」時,表示你必須將變數宣告在結構的大括號內,如下所示:
struct ContentView: View {
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 And Deli", "Waffle & Wolf", "Five Leaves", "Cafe Lore", "Confessional", "Barrafina", "Donostia", "Royal Oak", "CASK Pub and Kitchen"]
.
.
.
}
陣列是電腦程式設計中的基本資料結構,你可以將陣列想成是資料元素的集合。以上列程式碼的 restaurantNames 陣列來說,它表示了 String 元素的集合,你可將陣列視覺化為圖 6.5。

每個陣列元素都由索引值(index )來標示或存取,一個陣列中如果有 10 個元素,則有 0 至 9 的索引值。restaurantNames[0] 表示回傳陣列中的第一個項目。
我們繼續編寫程式碼,並更新 body 變數,如下所示:
var body: some View {
List {
ForEach(0...restaurantNames.count-1, id: \.self) { index in
Text(restaurantNames[index])
}
}
}
我們使用 ForEach 迴圈遍歷陣列的每一個項目。如前所述,陣列的第一個索引值是「0」,因此我們將範圍設定為0 至 restaurantNames.count-1,count 屬性回傳陣列中項目的總數,restaurantNames.count-1 的值是陣列的最後一個索引值。
為了顯示餐廳名稱,我們於 ForEach 的程式碼區塊中建立一個 Text 視圖,並將相應的餐廳名稱傳送給文字視圖。
當你更新程式碼後,預覽應該會顯示餐廳名稱的清單,如圖 6.6 所示。要捲動清單,你可以使用滑鼠游標來上下拖曳它。

我們還沒有將圖片加到每一列,首先下載取得範例圖片: http://www.appcoda.com/resources/swift53/simpletable-images1.zip,這個 zip 壓縮檔內有三個圖檔,解壓縮檔案, 並將圖片從 Finder 拖曳至素材目錄(Assets.xcassets ),如圖 6.7 所示。

現在編輯 ContentView,並將 Text 視圖替換為以下的 HStack 視圖:
HStack {
Image("restaurant")
.resizable()
.frame(width: 40, height: 40)
Text(restaurantNames[index])
}
我們使用 Image 視圖來載入餐廳圖片。為了調整圖片大小,我們使用 resizable 修飾器及 frame 修飾器來將圖片縮小至 40×40 點。
程式碼變更後,預覽應該會在每一列中顯示圖片,如圖 6.8 所示。

List 視圖在 iOS 15 內預設為使用「插入分組樣式」(Inset Grouped Style )。「插入分組清單樣式」顯示背景顏色,並在清單視圖的四周加入間距。如果要變更清單樣式,你可以將 listStyle 修飾器加到 List,如下所示:
List {
.
.
.
}
.listStyle(.plain)
要使用簡單的樣式,則可以設定為 .plain 或 PlainListStyle(),圖 6.9 為最後顯示的結果。

在本章結束之前,我希望你了解實作清單(及其他功能)有好幾種方式,現在我們在 ForEach 中指定restaurantNames 的索引範圍,如下所示:
ForEach(0...restaurantNames.count-1, id: \.self) { index in
.
.
.
}
實際上,你可以使用 .indices 屬性重寫程式碼,來取得可用項目的範圍:
ForEach(restaurantNames.indices, id: \.self) { index in
.
.
.
}
如果你更新程式中程式碼,將會得到相同的結果。還有另一種方式可以迴圈遍歷 restaurantNames 陣列中的項目,我們不使用索引,而是將整個陣列傳送給 ForEach,如下所示:
ForEach(restaurantNames, id: \.self) { restaurantName in
HStack {
Image("restaurant")
.resizable()
.frame(width: 40, height: 40)
Text(restaurantName)
}
}
在閉包中,有一個名為「restaurantName」的參數,此 restaurantName 參數儲存了迴圈的目前名稱,因此我們可以簡單地在 Text 視圖中使用它。
你可以試著變更你的程式碼,預覽應該是相同的。
現在範例 App 的所有儲存格都是顯示相同的圖片,試著調整 App 來讓各個儲存格顯示不同的圖片(提示:為圖片建立另一個陣列)。你可下載使用本章所準備的範例圖片 http://www.appcoda.com/resources/swift4/simpletable-images-2.zip ,圖 6.10 展示最後的結果畫面。

假使你不知道該如何完成這個作業,也不用擔心,我會在下一章中完整說明。
Credit: 範例中所使用的圖片是由unsplash.com提供。
「清單視圖」是 SwiftUI 中最常用的元件之一,如果你已徹底了解這些內容並成功建立 App,那麼你應該對如何建立自己的清單視圖有堅實的理解。
我試著讓這個範例 App 保持一切簡單,但是在真實世界的 App 中,清單視圖的資料通常不會「寫死」( hard-coded ),它一般是從檔案、資料庫或某處載入,之後的章節內容將會談到這部分。此時,請確認你已經完全理解清單視圖的工作原理,若是仍然感到困惑的話,請回到本章開頭並重新閱讀本章的內容。
本章所準備的範例檔中,有最後完整的Xcode 專案供下載:http://www.appcoda.com/resources/swift59/swiftui-list-view.zip.