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 版本。
要讓列表視圖可以展開,你只需要如此建立一個數據模型 (data model)。
struct MenuItem: Identifiable {
var id = UUID()
var name: String
var image: String
var subMenuItems: [MenuItem]?
}
在上面的程式碼中,我們有一個用來建造選單物件的結構 (struct)。要創建一個巢狀列表 (nested list),關鍵就是要加入一個屬性,包含子級的可選陣列 (optional array of children) 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) 查找子選單物件,並以大綱形式顯示。以下是範例程式碼:
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 應該會如此呈現大綱視圖:
使用 Inset Grouped 列表樣式
在 iOS 13 中,Apple 為 UITableView
帶來了一種新樣式 ── Inset Grouped,這個樣式會讓每個 section 的內容被包覆在一個圓角區塊內。但是,這種樣式不適用於 SwiftUI 框架中的 List
視圖。在即將發佈的 iOS 14 版本,Apple 就將這個新樣式添加到了 SwiftUI 列表中。
要使用這個新列表樣式,我們可添加 listStyle
修飾符 (modifier) 到 List
視圖,並向它傳遞 InsetGroupedListStyle
的實例:
List {
...
}
.listStyle(InsetGroupedListStyle())
如果你正確地跟著步驟做,列表視圖應該會變成 inset grouped 樣式:
總結
在本篇教學中,我們介紹了 SwiftUI 列表視圖的兩個新功能。如你在範例中可見,要建構一個大綱視圖或展開式列表視圖非常容易,你要做的就只是準備好一個正確的數據模型,列表視圖就會幫你完成剩下的工作,遍歷數據結構,並呈現大綱視圖。
除了這些新功能外,Apple 還在 iOS 14 中加入了另一種列表樣式 SidebarListStyle。而且,新的更新也提供了 OutlineGroup
和 DisclosureGroup
,讓我們可以進一步客製大綱視圖。我們將在另一篇教學文章中進一步研究這些功能,請密切關注我們的更新。
原文:Building an Expandable List View with Inset Grouped Style Using SwiftUI