使用 List 组件创建列表视图

了解如何使用 SwiftUI 的 List 组件,创建不同样式的列表视图。

使用 List 组件创建列表视图

List Style

insetGrouped 样式(默认,有边框)

如果未指定ListStyle,List 会根据设备类型选择默认样式。在 iOS 设备上,默认使用 insetGrouped 风格。

insetGrouped 带有边框和分割线Section 标题会相较于 List 内容略微靠右。

iOS 提醒
iOS 备忘录
iOS 备忘录

以下示例代码创建了一个类似于待办事项的 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 内容左对齐。

这种布局使每个列表项的显示效果简洁且清晰,非常适合信息展示型的应用。

💡
List 分割线默认只在文字内容之间添加,图片和 icon 下方不会有。
iOS 支持应用
iOS 通讯录

下面实例代码创建了一个类似于 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 上,insetplain 效果相同。

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) // 隐藏分割线
        }
    }
}

右箭头

SwiftUI NavigationLink Hide Arrow
Is there a way to hide the arrow to the right of the navigation link view that is automatically added? I want to show an image grid using NavigationView -> List -> HStack -> NavigationLink_1 -

以上方法可以隐藏。
但是如何想要结合使用 . 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)

交互