使用 List 组件创建列表视图
了解如何使用 SwiftUI 的 List 组件,创建不同样式的列表视图。
List Style
insetGrouped 样式(默认,有边框)
如果未指定ListStyle
,List 会根据设备类型选择默认样式。在 iOS 设备上,默认使用 insetGrouped
风格。
insetGrouped
带有边框和分割线,Section 标题会相较于 List 内容略微靠右。
以下示例代码创建了一个类似于待办事项的 UI 布局:
import SwiftUI
// 1. 数据模型
struct TodoItem: Identifiable {
let id = UUID()
let title: String
let isCompleted: Bool
let dueDate: String // 简化为字符串表示日期
}
// 2. 主视图
struct TodoListView: View {
// 示例数据
let todayTodos = [
TodoItem(title: "购买牛奶", isCompleted: false, dueDate: "今天"),
TodoItem(title: "完成SwiftUI项目", isCompleted: true, dueDate: "今天")
]
let tomorrowTodos = [
TodoItem(title: "健身房训练", isCompleted: false, dueDate: "明天"),
TodoItem(title: "阅读Swift书籍", isCompleted: false, dueDate: "明天")
]
let upcomingTodos = [
TodoItem(title: "参加会议", isCompleted: false, dueDate: "下周"),
TodoItem(title: "旅游计划", isCompleted: false, dueDate: "下月")
]
var body: some View {
NavigationView {
List {
// 今天的待办事项
Section(header: Text("今天").font(.headline)) {
ForEach(todayTodos) { todo in
HStack {
Image(systemName: todo.isCompleted ? "checkmark.circle.fill" : "circle")
.foregroundColor(todo.isCompleted ? .green : .gray)
Text(todo.title)
.strikethrough(todo.isCompleted, color: .gray)
.foregroundColor(todo.isCompleted ? .gray : .primary)
Spacer()
Text(todo.dueDate)
.font(.subheadline)
.foregroundColor(.secondary)
}
.padding(.vertical, 4)
}
}
// 明天的待办事项
Section(header: Text("明天").font(.headline)) {
ForEach(tomorrowTodos) { todo in
HStack {
Image(systemName: todo.isCompleted ? "checkmark.circle.fill" : "circle")
.foregroundColor(todo.isCompleted ? .green : .gray)
Text(todo.title)
.strikethrough(todo.isCompleted, color: .gray)
.foregroundColor(todo.isCompleted ? .gray : .primary)
Spacer()
Text(todo.dueDate)
.font(.subheadline)
.foregroundColor(.secondary)
}
.padding(.vertical, 4)
}
}
// 即将到来的待办事项
Section(header: Text("即将到来").font(.headline)) {
ForEach(upcomingTodos) { todo in
HStack {
Image(systemName: todo.isCompleted ? "checkmark.circle.fill" : "circle")
.foregroundColor(todo.isCompleted ? .green : .gray)
Text(todo.title)
.strikethrough(todo.isCompleted, color: .gray)
.foregroundColor(todo.isCompleted ? .gray : .primary)
Spacer()
Text(todo.dueDate)
.font(.subheadline)
.foregroundColor(.secondary)
}
.padding(.vertical, 4)
}
}
}
// .listStyle(InsetGroupedListStyle()) // 设置列表样式
.navigationTitle("待办事项")
}
}
}
// 3. 预览
struct TodoListView_Previews: PreviewProvider {
static var previews: some View {
TodoListView()
}
}
plain 样式(无边框)
plain
样式无分组边框,左右默认贴近屏幕边缘。
Section 标题会和 List 内容左对齐。
这种布局使每个列表项的显示效果简洁且清晰,非常适合信息展示型的应用。
下面实例代码创建了一个类似于 Apple 支持中的布局:
import SwiftUI
struct ListView: View {
let items = [
("如何在 Apple Watch 上使用两下手势", "4个月前", "clock.fill"),
("如何使用 Apple Watch", "4个月前", "clock.fill"),
("如何更新 Apple Watch", "4个月前", "clock.fill"),
("10个有趣的 Apple Watch 功能", "4个月前", "clock.fill"),
("如何恢复 Apple Watch", "4个月前", "clock.fill")
]
var body: some View {
List(items, id: \.0) { item in
HStack {
Image(systemName: item.2)
.resizable()
.frame(width: 40, height: 40)
.clipShape(RoundedRectangle(cornerRadius: 8))
.padding(.trailing, 8)
VStack(alignment: .leading) {
Text(item.0)
.font(.headline)
Text(item.1)
.font(.subheadline)
.foregroundColor(.secondary)
}
}
.padding(.vertical, 8)
}
.listStyle(.plain)
.navigationTitle("Apple Watch")
}
}
struct ListView_Previews: PreviewProvider {
static var previews: some View {
NavigationView {
ListView()
}
}
}
inset 样式
在 iOS 上,inset
和 plain
效果相同。
grouped 样式(分组无边框)
grouped
样式具有分组,但没有边框。
grouped
样式适合用于展示密集的信息,例如 iOS 添加付款方式的页面:
行间隔
可以通过自定义 .listRowInsets()
来调整每行之间的间距。
SwiftUI 的 List 默认为每行应用了一定的内边距(EdgeInsets):
.listRowInsets(
EdgeInsets(
top: 8, leading: 16, bottom: 8,trailing: 16)
)
你可以按照实际需求调整他。
例如,要略微减小行间距:
.listRowInsets(
EdgeInsets(top: 4, leading: 16, bottom: 4, trailing: 16)
)
分割线
隐藏分割线
SwiftUI 从 iOS 15 开始提供了 listRowSeparator(.hidden)
修饰符,可以用于隐藏分割线。
List {
Section(header: Text("今天").font(.headline)) {
ForEach(todayTodos) { todo in
HStack {
Image(systemName: todo.isCompleted ? "checkmark.circle.fill" : "circle")
.foregroundColor(todo.isCompleted ? .green : .gray)
Text(todo.title)
.strikethrough(todo.isCompleted, color: .gray)
.foregroundColor(todo.isCompleted ? .gray : .primary)
Spacer()
Text(todo.dueDate)
.font(.subheadline)
.foregroundColor(.secondary)
}
.padding(.vertical, 4)
.listRowSeparator(.hidden) // 隐藏分割线
}
}
}
右箭头
以上方法可以隐藏。
但是如何想要结合使用 . navigationTransition() 高级效果,就会出现动画问题。
颜色
自定义每行背景色
在 iOS 15 及以上版本,可以通过 .listRowBackground()
,为每一行设置颜色。
可以用于创建这样的彩色效果:
List {
// 今天的待办事项
Section(header: Text("今天").font(.headline)) {
ForEach(todayTodos) { todo in
HStack {
Image(systemName: todo.isCompleted ? "checkmark.circle.fill" : "circle")
.foregroundColor(todo.isCompleted ? .green : .white)
Text(todo.title)
.strikethrough(todo.isCompleted, color: .gray)
.foregroundColor(todo.isCompleted ? .gray : .white)
Spacer()
Text(todo.dueDate)
.font(.subheadline)
.foregroundColor(.secondary)
}
.padding(.vertical, 4)
.listRowBackground(Color.blue.opacity(0.5))
}
}
}
隐藏 Section 背景色
可以通过在 Section 内部设置.listRowBackground(Color.clear)
,将 Section
的背景设置为透明。
List {
// 今天的待办事项
Section(header: Text("今天").font(.headline)) {
ForEach(todayTodos) { todo in
HStack {
Image(systemName: todo.isCompleted ? "checkmark.circle.fill" : "circle")
.foregroundColor(todo.isCompleted ? .green : .gray)
Text(todo.title)
.strikethrough(todo.isCompleted, color: .gray)
.foregroundColor(todo.isCompleted ? .gray : .primary)
Spacer()
Text(todo.dueDate)
.font(.subheadline)
.foregroundColor(.secondary)
}
.padding(.vertical, 4)
.listRowBackground(Color.clear)
}
}
// 其他 section
xxx
}
粘性标题
当我们向上滑动的时候,List
的 section 标题会在上滑时自动固定在顶部并显示在导航栏下方,这种效果叫做 Section Header Pinned Behavior / sticky headers(粘性标题)。
当 listStyle 设置为以下选项时,粘性标题不生效:
.listStyle(.grouped)
.listStyle(.insetGrouped)
当 listStyle 设置为以下选项时,粘性标题生效:
.listStyle(.plain)
.listStyle(.inset)