如何讓 SwiftUI 的列表變得更加靈活
前言
List 可能是 SwiftUI 附帶的內(nèi)置視圖中最常用的一種,它使我們能夠在任何 Apple 平臺(tái)上呈現(xiàn)“類似于表格視圖”的用戶界面。今年,List 獲得了許多非常重要的升級(jí),使其更加靈活和易于定制。讓我們看看都有哪些新功能。
作為起點(diǎn),假設(shè)我們正在處理以下 ArticleList 視圖,該視圖使用 ArticleListViewModel 來(lái)呈現(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 編寫的,下面讓我們嘗試使用新功能來(lái)為我們的列表實(shí)現(xiàn)自定義樣式,并且使代碼更加健壯。
使用新速記語(yǔ)法
讓我們從一個(gè)很小的特性開始,這是一個(gè)非常受歡迎的變化,可以使用類似 enum 的速記語(yǔ)法來(lái)引用 SwiftUI 附帶的任何內(nèi)置 ListStyle 類型。比如,如果我們想將 “inset grouped” 樣式應(yīng)用于列表中,我們不需要拼出整個(gè) InsetGroupedListStyle 名稱,而是可以簡(jiǎn)單地將其稱為 .insetGrouped:
- struct ArticleList: View {
- @ObservedObject var viewModel: ArticleListViewModel
- var body: some View {
- List(viewModel.articles) { article in
- ...
- }
- .listStyle(.insetGrouped)
- }
- }
這樣的改變還是非常好的,可以讓我們的開發(fā)更加方便,閱讀時(shí)感覺(jué)更加自然。
元素綁定和自定義滑動(dòng)操作
接下來(lái),讓我們看看如何將完全自定義的滑動(dòng)操作添加到列表中。為了演示這種情況,我們?cè)?List 中嵌套一個(gè) ForEach (因?yàn)樵? SwiftUI 的中,列表變化一版都是由 ForEach 觸發(fā)的,而不是由 List 觸發(fā)的)。然后,讓我們使用另一個(gè)新功能,集合元素綁定,讓系統(tǒng)自動(dòng)為我們的 articles 數(shù)組中的每個(gè)元素創(chuàng)建一個(gè)可變綁定:
- 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)版本上運(yùn)行,也是沒(méi)有問(wèn)題的。完全向后兼容!
由于每個(gè) article 值在 ForEach 閉包中都是可變的,我們可以使用新的 swipeActions 修飾符來(lái)實(shí)現(xiàn)每個(gè) NavigationLink 項(xiàng)目視圖的自定義滑動(dòng)操作。在這種情況下,用戶可以輕松的在項(xiàng)目視圖上滑動(dòng)來(lái)決定喜不喜歡對(duì)應(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ù)喜歡還是不喜歡滑動(dòng)動(dòng)作來(lái)設(shè)置自定義顏色。
下拉刷新
就我個(gè)人而言,下拉刷新在我的 SwiftUI 功能請(qǐng)求列表中非常重要,所以我很高興看到今年的版本增加了對(duì)這種非常常見的 UI 范式的內(nèi)置支持。
不僅如此,下拉刷新是由 async/await 提供支持,不需要增加任何額外的代碼就可以讓系統(tǒng)知道什么時(shí)候重新加載結(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 中使用,請(qǐng)查看昨天的這篇文章[1],不要錯(cuò)過(guò)真正重要的“在 Swift 中認(rèn)識(shí) async/await[2]”WWDC 會(huì)議。
由于系統(tǒng)會(huì)自動(dòng)檢測(cè)知道 viewModel.reload() 何時(shí)調(diào)用完成,因此可以防止發(fā)生重復(fù)的刷新操作,并且可以更具狀態(tài)顯示和隱藏相應(yīng) UI。
可定制的分隔符
自從引入 SwiftUI 以來(lái),開發(fā)者們有一個(gè)非常普遍的要求,提供一個(gè) API ,用于隱藏或以其他自定義實(shí)現(xiàn)列表中每個(gè) item 之間的默認(rèn)分隔符。
很高興地告訴你,今年 Apple 已經(jīng)響應(yīng)了這個(gè)請(qǐng)求,我們可以使用新的 listRowSeparator 修飾符來(lái)完全隱藏不想呈現(xiàn)的分隔符:
- struct ArticleList: View {
- @ObservedObject var viewModel: ArticleListViewModel
- var body: some View {
- List {
- ForEach($viewModel.articles) { $article in
- NavigationLink(
- ...
- )
- .swipeActions {
- ...
- }
- .listRowSeparator(.hidden)
- }
- }
- ...
- }
- }
由于上述修飾符是在每個(gè)列表的 item 上調(diào)用的,而不是在列表本身上調(diào)用,這為我們提供了很大的靈活性,可以根據(jù)想要構(gòu)建的 UI 類型動(dòng)態(tài)隱藏或顯示每個(gè)分隔符。
還有另外一個(gè) API 用于控制部分分隔符的外觀顏色,可以使用自定義顏色為分隔符設(shè)置顏色——代碼如下:
- struct ArticleList: View {
- @ObservedObject var viewModel: ArticleListViewModel
- var body: some View {
- List {
- ForEach($viewModel.articles) { $article in
- NavigationLink(
- ...
- )
- .swipeActions {
- ...
- }
- .listRowSeparatorTint(.blue)
- }
- }
- ...
- }
- }
同樣,由于上述修飾符是在每個(gè)列表的 item 上調(diào)用的,可以為不同的分隔符設(shè)置不同的顏色。
總結(jié)
SwiftUI 正在變得更加靈活和強(qiáng)大,后面我將繼續(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 中認(rèn)識(shí) 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)載自微信公眾號(hào)「 Swift社區(qū)」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系 Swift社區(qū)公眾號(hào)。