如何讓 SwiftUI 的列表變得更加靈活
前言
List 可能是 SwiftUI 附帶的內(nèi)置視圖中最常用的一種,它使我們能夠在任何 Apple 平臺上呈現(xiàn)“類似于表格視圖”的用戶界面。今年,List 獲得了許多非常重要的升級,使其更加靈活和易于定制。讓我們看看都有哪些新功能。
作為起點,假設(shè)我們正在處理以下 ArticleList 視圖,該視圖使用 ArticleListViewModel 來呈現(xiàn)文章列表:
- struct ArticleList: View {
- @ObservedObject var viewModel: ArticleListViewModel
- var body: some View {
- List(viewModel.articles) { article in
- NavigationLink(
- destination: ArticleView(article: article),
- label: {
- VStack(alignment: .leading) {
- Text(article.title)
- .font(.headline)
- Text(article.description)
- .foregroundColor(.secondary)
- }
- }
- )
- }
- }
- }
上面的內(nèi)容目前是使用 SwiftUI 中初版的概念和 API 編寫的,下面讓我們嘗試使用新功能來為我們的列表實現(xiàn)自定義樣式,并且使代碼更加健壯。
使用新速記語法
讓我們從一個很小的特性開始,這是一個非常受歡迎的變化,可以使用類似 enum 的速記語法來引用 SwiftUI 附帶的任何內(nèi)置 ListStyle 類型。比如,如果我們想將 “inset grouped” 樣式應(yīng)用于列表中,我們不需要拼出整個 InsetGroupedListStyle 名稱,而是可以簡單地將其稱為 .insetGrouped:
- struct ArticleList: View {
- @ObservedObject var viewModel: ArticleListViewModel
- var body: some View {
- List(viewModel.articles) { article in
- ...
- }
- .listStyle(.insetGrouped)
- }
- }
這樣的改變還是非常好的,可以讓我們的開發(fā)更加方便,閱讀時感覺更加自然。
元素綁定和自定義滑動操作
接下來,讓我們看看如何將完全自定義的滑動操作添加到列表中。為了演示這種情況,我們在 List 中嵌套一個 ForEach (因為在 SwiftUI 的中,列表變化一版都是由 ForEach 觸發(fā)的,而不是由 List 觸發(fā)的)。然后,讓我們使用另一個新功能,集合元素綁定,讓系統(tǒng)自動為我們的 articles 數(shù)組中的每個元素創(chuàng)建一個可變綁定:
- struct ArticleList: View {
- @ObservedObject var viewModel: ArticleListViewModel
- var body: some View {
- List {
- ForEach($viewModel.articles) { $article in
- ...
- }
- }
- .listStyle(.insetGrouped)
- }
- }
注意:關(guān)于上述創(chuàng)建集合元素綁定的新方法,即使我們的應(yīng)用程序在較舊的操作系統(tǒng)版本上運行,也是沒有問題的。完全向后兼容!
由于每個 article 值在 ForEach 閉包中都是可變的,我們可以使用新的 swipeActions 修飾符來實現(xiàn)每個 NavigationLink 項目視圖的自定義滑動操作。在這種情況下,用戶可以輕松的在項目視圖上滑動來決定喜不喜歡對應(yīng)的文章:
- struct ArticleList: View {
- @ObservedObject var viewModel: ArticleListViewModel
- var body: some View {
- List {
- ForEach($viewModel.articles) { $article in
- NavigationLink(
- ...
- )
- .swipeActions {
- Button(
- action: {
- article.isFavorite.toggle()
- },
- label: {
- if article.isFavorite {
- Label("Remove from favorites",
- systemImage: "star.slash"
- )
- } else {
- Label("Add to favorites",
- systemImage: "star"
- )
- }
- }
- )
- .tint(article.isFavorite ? .red : .green)
- }
- }
- }
- .listStyle(.insetGrouped)
- }
- }
這里還可以使用新的 tint 修飾符根據(jù)喜歡還是不喜歡滑動動作來設(shè)置自定義顏色。
下拉刷新
就我個人而言,下拉刷新在我的 SwiftUI 功能請求列表中非常重要,所以我很高興看到今年的版本增加了對這種非常常見的 UI 范式的內(nèi)置支持。
不僅如此,下拉刷新是由 async/await 提供支持,不需要增加任何額外的代碼就可以讓系統(tǒng)知道什么時候重新加載結(jié)束。在列表中使用 refreshable 修飾符就可以完成,然后使用該修飾符的閉包 await 調(diào)用視圖模型的異步 reload 方法:
- struct ArticleList: View {
- @ObservedObject var viewModel: ArticleListViewModel
- var body: some View {
- List {
- ...
- }
- .listStyle(.insetGrouped)
- .refreshable {
- await viewModel.reload()
- }
- }
- }
要了解有關(guān) async/await 的更多信息以及如何在 SwiftUI 中使用,請查看昨天的這篇文章[1],不要錯過真正重要的“在 Swift 中認識 async/await[2]”WWDC 會議。
由于系統(tǒng)會自動檢測知道 viewModel.reload() 何時調(diào)用完成,因此可以防止發(fā)生重復(fù)的刷新操作,并且可以更具狀態(tài)顯示和隱藏相應(yīng) UI。
可定制的分隔符
自從引入 SwiftUI 以來,開發(fā)者們有一個非常普遍的要求,提供一個 API ,用于隱藏或以其他自定義實現(xiàn)列表中每個 item 之間的默認分隔符。
很高興地告訴你,今年 Apple 已經(jīng)響應(yīng)了這個請求,我們可以使用新的 listRowSeparator 修飾符來完全隱藏不想呈現(xiàn)的分隔符:
- struct ArticleList: View {
- @ObservedObject var viewModel: ArticleListViewModel
- var body: some View {
- List {
- ForEach($viewModel.articles) { $article in
- NavigationLink(
- ...
- )
- .swipeActions {
- ...
- }
- .listRowSeparator(.hidden)
- }
- }
- ...
- }
- }
由于上述修飾符是在每個列表的 item 上調(diào)用的,而不是在列表本身上調(diào)用,這為我們提供了很大的靈活性,可以根據(jù)想要構(gòu)建的 UI 類型動態(tài)隱藏或顯示每個分隔符。
還有另外一個 API 用于控制部分分隔符的外觀顏色,可以使用自定義顏色為分隔符設(shè)置顏色——代碼如下:
- struct ArticleList: View {
- @ObservedObject var viewModel: ArticleListViewModel
- var body: some View {
- List {
- ForEach($viewModel.articles) { $article in
- NavigationLink(
- ...
- )
- .swipeActions {
- ...
- }
- .listRowSeparatorTint(.blue)
- }
- }
- ...
- }
- }
同樣,由于上述修飾符是在每個列表的 item 上調(diào)用的,可以為不同的分隔符設(shè)置不同的顏色。
總結(jié)
SwiftUI 正在變得更加靈活和強大,后面我將繼續(xù)探索更多新推出的 API。
譯自 How SwiftUI’s List is becoming much more flexible this year[3]
參考資料
[1]Calling async APIs from a synchronous context:
https://wwdcbysundell.com/2021/calling-async-apis-from-synchronous-contexts/
[2]在 Swift 中認識 async/await:
https://developer.apple.com/videos/play/wwdc2021/10132/
[3]How SwiftUI’s List is becoming much more flexible this year:
https://wwdcbysundell.com/2021/exploring-new-swiftui-list-apis/#new-shorthand-syntax-for-applying-styling-protocols
本文轉(zhuǎn)載自微信公眾號「 Swift社區(qū)」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系 Swift社區(qū)公眾號。