SwiftUI 列表視圖 (list view) 和 UIKit 的 UITableView 很類似。在 SwiftUI 最初的版本中,Apple 的工程師已經把建立列表視圖的過程變得輕而易舉,我們不需要創建 prototype cell,也不需要委派 (delegate) 或 data source 的協定。我們只需要用幾行程式碼,就可以使用客製化單元格來建構一個列表視圖。
在 iOS 14 (和以上版本),Apple 繼續改善列表視圖,並添加了一些新功能。在本篇教學文章中,我們將會看看如何建構一個展開式列表視圖 (expandable list view) 或大綱視圖 (outline view),並探索 inset grouped 的列表樣式 (list style)。
首先,讓我們看看本篇教學的成品。我十分喜歡 La Marzocco,所以我用了它網站的導覽選單 (navigation menu) 為例子。以下的列表視圖展示了選單的大綱,使用者可以點擊顯示按鈕來展開列表。
當然,你也可以用自己的實作方法,來建構這個大綱視圖。但在最新版本的 SwiftUI 中,Apple 讓開發者可以更簡單地建構這種大綱視圖,並自動適應於 iOS、iPadOS、和 macOS 版本。
要繼續閱讀本章的教學的話,請先從 https://www.appcoda.com/resources/swiftui/expandablelist-images.zip 下載範例圖片。 然後,在 Xcode 使用 App 模板創建一個新的 SwiftUI 項目。 我將項目命名為 SwiftUIExpandableList,但您可以隨意將名稱設置為任何名稱。
建立項目後,解壓縮圖像存檔並將圖像添加到 Assets。在項目導航器中,右鍵單擊 SwiftUIExpandableList 並選擇創建一個新文件檔。 選擇 Swift File 模板並將其命名為 MenuItem.swift。
要讓列表視圖可以展開,你只需要如此建立一個數據模型 (data model)。請加入以下程式碼至 MenuItem.swift
:
struct MenuItem: Identifiable {
var id = UUID()
var name: String
var image: String
var subMenuItems: [MenuItem]?
}
在上面的程式碼中,我們有一個用來建造選單物件的結構 (struct)。要創建一個巢狀列表 (nested list),關鍵就是要加入一個屬性,包含子級的可選陣列 (optional array) subMenuItems
。請注意,子級與其父級的型別 (type) 是一樣的。
對於頂層 (top level) 選單物件,我們可以如此創建一個 MenuItem
陣列:
// Main menu items
let sampleMenuItems = [ MenuItem(name: "Espresso Machines", image: "linea-mini", subMenuItems: espressoMachineMenuItems),
MenuItem(name: "Grinders", image: "swift-mini", subMenuItems: grinderMenuItems),
MenuItem(name: "Other Equipment", image: "espresso-ep", subMenuItems: otherMenuItems)
]
我們會針對每個選單物件,指定子選單 (sub-menu) 物件的陣列。如果沒有子選單物件,則可以省略 subMenuItems
參數,或傳遞 nil
值。我們可以這樣定義子選單物件:
// Sub-menu items for Espressco Machines
let espressoMachineMenuItems = [ MenuItem(name: "Leva", image: "leva-x", subMenuItems: [ MenuItem(name: "Leva X", image: "leva-x"), MenuItem(name: "Leva S", image: "leva-s") ]),
MenuItem(name: "Strada", image: "strada-ep", subMenuItems: [ MenuItem(name: "Strada EP", image: "strada-ep"), MenuItem(name: "Strada AV", image: "strada-av"), MenuItem(name: "Strada MP", image: "strada-mp"), MenuItem(name: "Strada EE", image: "strada-ee") ]),
MenuItem(name: "KB90", image: "kb90"),
MenuItem(name: "Linea", image: "linea-pb-x", subMenuItems: [ MenuItem(name: "Linea PB X", image: "linea-pb-x"), MenuItem(name: "Linea PB", image: "linea-pb"), MenuItem(name: "Linea Classic", image: "linea-classic") ]),
MenuItem(name: "GB5", image: "gb5"),
MenuItem(name: "Home", image: "gs3", subMenuItems: [ MenuItem(name: "GS3", image: "gs3"), MenuItem(name: "Linea Mini", image: "linea-mini") ])
]
// Sub-menu items for Grinder
let grinderMenuItems = [ MenuItem(name: "Swift", image: "swift"),
MenuItem(name: "Vulcano", image: "vulcano"),
MenuItem(name: "Swift Mini", image: "swift-mini"),
MenuItem(name: "Lux D", image: "lux-d")
]
// Sub-menu items for other equipment
let otherMenuItems = [ MenuItem(name: "Espresso AV", image: "espresso-av"),
MenuItem(name: "Espresso EP", image: "espresso-ep"),
MenuItem(name: "Pour Over", image: "pourover"),
MenuItem(name: "Steam", image: "steam")
]
準備好數據模型後,我們可以編寫程式碼以呈現列表視圖。List
視圖現在有了可選的 children
參數。所以如果有任何子物件,你可以提供其 key path \.subMenuItems
。 然後,SwiftUI 將遞歸 (recursively) 查找子選單物件,並以大綱形式顯示。打開 ContentView.swift
並在 body
中插入以下程式碼:
List(sampleMenuItems, children: \.subMenuItems) { item in
HStack {
Image(item.image)
.resizable()
.scaledToFit()
.frame(width: 50, height: 50)
Text(item.name)
.font(.system(.title3, design: .rounded))
.bold()
}
}
在 List
視圖閉包中,我們會描述每一行的外觀。在範例程式碼中,我們使用了 HStack
佈局圖像和文本視圖。如果你已經在 ContentView
中正確地添加了程式碼的話,SwiftUI 應該會如此呈現大綱視圖:
要測試App,請在模擬器或預覽畫布中運行它。 您可以點擊披露指示器以顯示子菜單。
在 iOS 中,Apple 將列表視圖的預設樣式設置為 Inset Grouped,其中分組的部分以圓角嵌入。 如果要將其切換回普通列表樣式,可以將 .listStyle
修飾器附加到 List
視圖並將其值設置為 .plain
,如下所示:
List {
...
}
.listStyle(.plain)
如果你沒有加錯位置,列表視圖現在應該更改為普通樣式。
正如您在前面的示例中所見,使用 List
視圖創建大綱視圖非常容易。 但是,如果您想更好地控制大綱視圖的外觀(例如添加部分標題),則需要使用 OutlineGroup
。 這個視圖用於呈現數據的層次結構。
如果您已明白如何構建可擴展的列表視圖,「OutlineGroup」的用法也非常相似。 例如,以下程式碼允許您構建相同的可擴展列表視圖,如圖 28.1 所示:
List {
OutlineGroup(sampleMenuItems, children: \.subMenuItems) { item in
HStack {
Image(item.image)
.resizable()
.scaledToFit()
.frame(width: 50, height: 50)
Text(item.name)
.font(.system(.title3, design: .rounded))
.bold()
}
}
}
和 List
視圖類似,您只需要在建立OutlineGroup
時傳入要顯示的項目並指定子菜單項(或子項)的鍵路徑(Key path)。
有了 OutlineGroup
,您可以更好地控制大綱視圖的外觀。 例如,我們希望將頂級菜單項顯示為節標題。 你可以這樣寫程式碼:
List {
ForEach(sampleMenuItems) { menuItem in
Section(header:
HStack {
Text(menuItem.name)
.font(.title3)
.fontWeight(.heavy)
Image(menuItem.image)
.resizable()
.scaledToFit()
.frame(width: 30, height: 30)
}
.padding(.vertical)
) {
OutlineGroup(menuItem.subMenuItems ?? [MenuItem](), children: \.subMenuItems) { item in
HStack {
Image(item.image)
.resizable()
.scaledToFit()
.frame(width: 50, height: 50)
Text(item.name)
.font(.system(.title3, design: .rounded))
.bold()
}
}
}
}
}
在上面的程式碼中,我們使用 ForEach
來遍歷菜單項。 我們將頂層項目呈現為節標題。 對於其餘的子菜單項,我們依靠「OutlineGroup」來創建數據層次結構。 如果您更改 ContentView.swift
內的程式碼,您應該會看到如圖 28.4 所示的大綱視圖。
同樣地,如果您更喜歡使用普通列表樣式,可以將 listStyle
修飾器附加到 List
視圖:
.listStyle(.plain)
然後你會得到圖 28.5 的結果。
在大綱視圖中,您可以通過點擊顯示指示器來顯示/隱藏子菜單。 無論您使用 List
還是 OutlineGroup
來實現可展開列表,iOS 中引入的名為 DisclosureGroup
的新視圖都支持這種「展開和折疊」功能。
公開組視圖(Disclosure Group View)是為顯示或隱藏另一個內容視圖而設計。 雖然 DisclosureGroup
會自動嵌入到 OutlineGroup
中,但您也可以獨立使用此視圖。 例如,您可以使用以下程式碼來顯示和隱藏問題和答案:
DisclosureGroup(
content: {
Text("Absolutely! You are allowed to reuse the source code in your own projects (personal/commercial). However, you're not allowed to distribute or sell the source code without prior authorization.")
.font(.body)
.fontWeight(.light)
},
label: {
Text("1. Can I reuse the source code?")
.font(.body)
.bold()
.foregroundColor(.black)
}
)
公開組視圖有兩個參數:label 和 content。 在上面的程式碼中,我們在 label
參數中指定了問題,在 content
參數中指定了答案。 圖 28.6 顯示了結果。
在預設的情況下,公開組視圖處於隱藏模式。 要顯示內容視圖,請點擊顯示指示器(>)以將顯示組視圖切換到「展開」狀態。
另外,您可以通過傳一個綁定來控制 DisclosureGroup
的狀態,該綁定直接控制披露指示器的狀態(展開或折疊),如下所示:
struct FaqView: View {
@State var showContent = true
var body: some View {
DisclosureGroup(
isExpanded: $showContent,
content: {
...
},
label: {
...
}
)
.padding()
}
}
DisclosureGroup
視圖允許您更好地控制披露指標的狀態。 你的練習是建立一個類似於圖 28.7 所示的 FAQ 屏幕。
使用者可以點擊披露指示器來顯示或隱藏單個問題。 此外,App也提供了一個「Show all」按鈕來展開所有問題並立即顯示答案。
在本章中,我介紹了 SwiftUI 的一些新功能。 正如您在演示中看到的那樣,構建大綱視圖或可擴展列表視圖一點也不難。 您需要做的就是定義一個正確的數據模型。 List 視圖處理其餘部分並呈現大綱視圖。 最重要的是,新的更新提供了 OutlineGroup
和 DisclosureGroup
供您進一步客製化大綱視圖。
在本章所準備的範例檔中,有最後完整的 Xcode 專案,可供你下載參考:
https://www.appcoda.com/resources/swiftui5/SwiftUIExpandableList.zip
請注意,您可以參考 FaqView.swift
來獲得練習的解決方案。