List — 是 SwiftUI 中的一个容器 View,用于以垂直可滚动列表的形式显示数据,相当于 UIKit 中的 UITableView。根据 Apple Developer Documentation, 2024,List 支持静态和动态分区、滑动操作、行拖放和 pull-to-refresh。与 UITableView 不同,List 使用基于 SwiftUI 和 ForEach 的声明式 API,自动管理单元格复用和大量行时的性能。
要点
List — 是一个 View,用于在垂直可滚动列表中显示元素序列。它随 iOS 13 和 SwiftUI 一起出现,是显示数据列表的主要方式,取代了 UIKit 中的 UITableView。List 自动管理单元格复用、滚动和性能。
List 使用懒加载:单元格在滚动时创建,而不是一次性全部创建。这将其与 ScrollView 内使用 ForEach 的 VStack 区分开来,后者所有单元格都在渲染时创建。List 还提供对滑动操作、pull-to-refresh、编辑(删除/移动)和行选择的内置支持。
根据 Apple WWDC 2021 (Session 10072),iOS 15+ 中的 List 由于新的集合级 diffing 机制,获得了显著的性能改进。这使得 List 在更新数据时更加高效,特别是对于数百行的列表。
开发人员经常在 List 和 ScrollView 与 VStack 之间选择来显示一组 Views。关键区别:List 使用单元格复用(如 UITableView),而 ScrollView + VStack 一次性创建所有 Views。对于固定数量元素(最多 20 个)的列表,差异可以忽略不计。对于 50+ 行的动态列表,List 在性能方面更优。
静态 List — 是具有固定行数的列表,行数直接在 List 主体中指定。用于菜单、设置和具有已知元素集的表单。每行都显式声明,无需循环或 ForEach。
// 静态列表(用于菜单和设置)
List {
Text("个人资料")
Text("设置")
Text("关于")
}
// 动态列表(用于数据)
struct UserList: View {
let users: [User]
var body: some View {
List(users) { user in
HStack {
Text(user.name)
Text(user.role)
.foregroundColor(.secondary)
}
}
}
}
动态 List 使用 List(data:rowContent:) 初始化器或 List 主体内的 ForEach。第一种方式在每个行对应一个数据元素时很方便。第二种方式在数据之间有分区或额外元素时使用。
标识 (Identifiable): 对于动态列表,数据元素必须符合 Identifiable 协议,或者在 data:id 元组中指定指向唯一标识符的 KeyPath。SwiftUI 使用标识符跟踪变化:添加、删除和移动行。
Section — 是一个 View,用于在 List 中分组行,带有标题和可选的页脚。Section 将 header 和 footer 作为 ViewBuilder 接受,允许不仅使用文本,还可以使用自定义 Views 作为分区标题。
struct SettingsView: View {
var body: some View {
List {
Section(header: Text("账户")) {
Text("姓名")
Text("电子邮件")
}
Section(header: Text("通知")) {
Toggle("Push", isOn: $pushEnabled)
Toggle("Email", isOn: $emailEnabled)
}
}
.listStyle(.insetGrouped)
}
}
// 使用 ForEach 的动态分区
List {
ForEach(groupedData.keys.sorted(), id: \.self) { key in
Section(header: Text(key)) {
ForEach(groupedData[key]!) { item in
Text(item.title)
}
}
}
}
列表样式: SwiftUI 通过 .listStyle() 修饰符提供几种内置样式。.insetGrouped — iOS Settings 的标准样式,.plain — 极简风格,.inset — 带缩进,.sidebar — 用于 iPad 上的 Sidebar。
根据 SwiftUI Cookbook (2024),带有动态分区和内部 ForEach 的 Section 是复杂结构应用程序中数据分组的标准模式。关键规则:不要将 Section 嵌套在 Section 内,也不要将 List(data:) 初始化器与 Section 一起使用 — 在 List 主体内使用 ForEach。
.swipeActions(edge:allowsFullSwipe:content:) — iOS 15+ 的修饰符,为 List 行添加滑动操作。允许在向左(默认)或向右滑动时显示按钮,具有不同的颜色和角色(destructive, cancel)。
struct TaskList: View {
@Binding var tasks: [Task]
var body: some View {
List {
ForEach($tasks) { $task in
Text(task.title)
.swipeActions(edge: .trailing) {
Button("删除", role: .destructive) {
tasks.removeAll { $0.id == task.id }
}
}
.swipeActions(edge: .leading) {
Button(task.isDone ? "撤销" : "完成") {
task.isDone.toggle()
}
.tint(.green)
}
}
}
.refreshable {
// 异步数据加载
await loadTasks()
}
}
}
.refreshable — iOS 15+ 的修饰符,添加 pull-to-refresh。接受一个异步闭包 (async),在用户下拉列表时执行。SwiftUI 自动显示加载指示器。操作完成后指示器隐藏。
.onDelete 和 .onMove — iOS 13+ 的修饰符,添加对删除和移动行的支持。要使用它们,将数据包装在带 Binding 的 ForEach 中,或通过 List 或 ForEach 上的 .onDelete(perform:) 传递闭包。
List 性能 取决于行数、每个单元格的复杂度和数据更新频率。SwiftUI 使用懒加载和单元格复用(类似于 UITableView.dequeueReusableCell),但对于 500+ 行的列表可能需要额外优化。
| 优化 | 描述 | iOS 版本 |
|---|---|---|
| Identifiable | 每个元素的唯一 ID | iOS 13+ |
| EquatableView | 在数据相等时避免重新渲染 | iOS 13+ |
| id(_:) | ID 更改时强制重新创建 View | iOS 13+ |
| .equatable() | 严格按 Equatable 比较 | iOS 15+ |
| Diffable data | 更改时自动 diff | iOS 15+ |
问题 1:频繁更新。 如果列表中的数据频繁更新(例如每秒),List 可能会在每次状态更改时重新渲染可见单元格。解决方案:对数据使用结构体(value types)—— SwiftUI 按值比较它们,仅重新渲染更改过的行。
问题 2:重型单元格。 如果每行包含复杂的 View 层次结构、图像和动画,滚动可能会变慢。解决方案:将单元格移动到单独的 Views 中,使用 EquatableView 防止不必要的重新渲染。根据 SwiftUI Lab (2024),将复杂行拆分为子组件可将渲染时间减少 30–50%。
问题 3:大量行。 在 1000+ 行时,List 由于懒加载仍然高效工作,但初始加载可能因布局计算而变慢。解决方案:仅对不需要 List 功能(滑动、分区)的同类行列表使用 LazyVStack。对于全功能列表,List 仍然是最佳选择。
常见问题
List — 用于在 SwiftUI 中显示可滚动数据列表的容器 View。相当于 UIKit 中带声明式 API 的 UITableView。支持分区、滑动操作、pull-to-refresh、编辑和通过 .listStyle() 自定义。
List 使用 懒加载和单元格复用 — 单元格在滚动时创建。ScrollView + VStack 一次性创建所有 Views。对于 50+ 行的列表,List 更优。对于固定的小集合(最多 20 个元素),差异可以忽略不计。
使用 .refreshable (iOS 15+) 修饰符。传递一个带有数据更新逻辑的异步闭包。SwiftUI 自动显示加载指示器,并在异步操作完成后隐藏它。
使用带标题和可选页脚的 Section View。将列表行放在 Section 内部。对于动态分区,使用带 groupedData 的 ForEach。列表样式通过 .listStyle(.insetGrouped) 配置以获得类似 iOS 的外观。
对数据使用 结构体(value types),将复杂单元格移动到带 EquatableView 的单独 Views 中,避免每行的频繁状态更新。对于 1000+ 行的列表,如果不需要 List 功能,请考虑使用 LazyVStack。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。