如何让 SwiftUI 的列表变得更加灵活

2021-11-26 15:17:29 浏览数 (1)

前言

List 可能是 SwiftUI 附带的内置视图中最常用的一种,它使我们能够在任何 Apple 平台上呈现“类似于表格视图”的用户界面。今年,List 获得了许多非常重要的升级,使其更加灵活和易于定制。让我们看看都有哪些新功能。

作为起点,假设我们正在处理以下 ArticleList 视图,该视图使用 ArticleListViewModel 来呈现文章列表:

代码语言:javascript复制
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)
                    }
                }
            )
        }
    }
}

上面的内容目前是使用 SwiftUI 中初版的概念和 API 编写的,下面让我们尝试使用新功能来为我们的列表实现自定义样式,并且使代码更加健壮。

使用新速记语法

让我们从一个很小的特性开始,这是一个非常受欢迎的变化,可以使用类似 enum 的速记语法来引用 SwiftUI 附带的任何内置 ListStyle 类型。比如,如果我们想将 “inset grouped” 样式应用于列表中,我们不需要拼出整个 InsetGroupedListStyle 名称,而是可以简单地将其称为 .insetGrouped

代码语言:javascript复制
struct ArticleList: View {
    @ObservedObject var viewModel: ArticleListViewModel

    var body: some View {
        List(viewModel.articles) { article in
            ...
        }
        .listStyle(.insetGrouped)
    }
}

这样的改变还是非常好的,可以让我们的开发更加方便,阅读时感觉更加自然。

元素绑定和自定义滑动操作

接下来,让我们看看如何将完全自定义的滑动操作添加到列表中。为了演示这种情况,我们在 List 中嵌套一个 ForEach (因为在 SwiftUI 的中,列表变化一版都是由 ForEach 触发的,而不是由 List 触发的)。然后,让我们使用另一个新功能,集合元素绑定,让系统自动为我们的 articles 数组中的每个元素创建一个可变绑定:

代码语言:javascript复制
struct ArticleList: View {
    @ObservedObject var viewModel: ArticleListViewModel

    var body: some View {
        List {
            ForEach($viewModel.articles) { $article in
                ...
            }
        }
        .listStyle(.insetGrouped)
    }
}

注意:关于上述创建集合元素绑定的新方法,即使我们的应用程序在较旧的操作系统版本上运行,也是没有问题的。完全向后兼容!

由于每个 article 值在 ForEach 闭包中都是可变的,我们可以使用新的 swipeActions 修饰符来实现每个 NavigationLink 项目视图的自定义滑动操作。在这种情况下,用户可以轻松的在项目视图上滑动来决定喜不喜欢对应的文章:

代码语言:javascript复制
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 修饰符根据喜欢还是不喜欢滑动动作来设置自定义颜色。

下拉刷新

就我个人而言,下拉刷新在我的 SwiftUI 功能请求列表中非常重要,所以我很高兴看到今年的版本增加了对这种非常常见的 UI 范式的内置支持。

不仅如此,下拉刷新是由 async/await 提供支持,不需要增加任何额外的代码就可以让系统知道什么时候重新加载结束。在列表中使用 refreshable 修饰符就可以完成,然后使用该修饰符的闭包 await 调用视图模型的异步 reload 方法:

代码语言:javascript复制
struct ArticleList: View {
    @ObservedObject var viewModel: ArticleListViewModel

    var body: some View {
        List {
            ...
        }
        .listStyle(.insetGrouped)
        .refreshable {
            await viewModel.reload()
        }
    }
}

要了解有关 async/await 的更多信息以及如何在 SwiftUI 中使用,请查看昨天的这篇文章[1],不要错过真正重要的“在 Swift 中认识 async/await[2]”WWDC 会议。

由于系统会自动检测知道 viewModel.reload() 何时调用完成,因此可以防止发生重复的刷新操作,并且可以更具状态显示和隐藏相应 UI。

可定制的分隔符

自从引入 SwiftUI 以来,开发者们有一个非常普遍的要求,提供一个 API ,用于隐藏或以其他自定义实现列表中每个 item 之间的默认分隔符。

很高兴地告诉你,今年 Apple 已经响应了这个请求,我们可以使用新的 listRowSeparator 修饰符来完全隐藏不想呈现的分隔符:

代码语言:javascript复制
struct ArticleList: View {
    @ObservedObject var viewModel: ArticleListViewModel

    var body: some View {
        List {
            ForEach($viewModel.articles) { $article in
                NavigationLink(
                    ...
                )
                .swipeActions {
                    ...
                }
                .listRowSeparator(.hidden)
            }
        }
        ...
    }
}

由于上述修饰符是在每个列表的 item 上调用的,而不是在列表本身上调用,这为我们提供了很大的灵活性,可以根据想要构建的 UI 类型动态隐藏或显示每个分隔符。

还有另外一个 API 用于控制部分分隔符的外观颜色,可以使用自定义颜色为分隔符设置颜色——代码如下:

代码语言:javascript复制
struct ArticleList: View {
    @ObservedObject var viewModel: ArticleListViewModel

    var body: some View {
        List {
            ForEach($viewModel.articles) { $article in
                NavigationLink(
                    ...
                )
                .swipeActions {
                    ...
                }
                .listRowSeparatorTint(.blue)
            }
        }
        ...
    }
}

同样,由于上述修饰符是在每个列表的 item 上调用的,可以为不同的分隔符设置不同的颜色。

总结

SwiftUI 正在变得更加灵活和强大,后面我将继续探索更多新推出的 API,并在这里发布分享,欢迎持续关注,为了防止丢失,建议为本号设置星标

转载声明

本文已经在公众号 Swift社区 发布,需要转载请联系小编申请开白。未经同意禁止私自转载。

译自 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

0 人点赞