SwiftUIView layout

Lists

SwiftUI Lists 组件的全面学习笔记,涵盖基础用法、数据驱动、选择、层级结构、编辑和样式定制

概述

List 是 SwiftUI 中用于展示单列数据行的容器视图,支持选择一个或多个成员。它是构建数据列表界面的核心组件,适用于所有 Apple 平台(iOS 13.0+、macOS 10.15+、watchOS 6.0+、tvOS 13.0+、visionOS 1.0+)。

基础用法

静态列表

最简单的 List 使用静态内容创建:

var body: some View {
    List {
        Text("第一项")
        Text("第二项")
        Text("第三项")
    }
}

数据驱动列表

使用 Identifiable 数据

当数据类型遵循 Identifiable 协议时,可以直接创建列表:

struct Ocean: Identifiable {
    let name: String
    let id = UUID()
}

private var oceans = [
    Ocean(name: "太平洋"),
    Ocean(name: "大西洋"),
    Ocean(name: "印度洋"),
    Ocean(name: "南冰洋"),
    Ocean(name: "北冰洋")
]

var body: some View {
    List(oceans) { ocean in
        Text(ocean.name)
    }
}

使用 KeyPath 指定标识符

对于不遵循 Identifiable 的数据,可以通过 id 参数指定标识符的 KeyPath:

struct Country {
    let name: String
    let code: String
}

private var countries = [
    Country(name: "中国", code: "CN"),
    Country(name: "美国", code: "US")
]

var body: some View {
    List(countries, id: \.code) { country in
        Text(country.name)
    }
}

选择支持

单选

通过绑定单个 ID 值实现单选:

struct Ocean: Identifiable, Hashable {
    let name: String
    let id = UUID()
}

@State private var singleSelection: UUID?

var body: some View {
    NavigationView {
        List(oceans, selection: $singleSelection) { ocean in
            Text(ocean.name)
        }
        .navigationTitle("海洋")
        .toolbar { EditButton() }
    }
}

多选

通过绑定 Set 类型实现多选:

@State private var multiSelection = Set<UUID>()

var body: some View {
    NavigationView {
        List(oceans, selection: $multiSelection) { ocean in
            Text(ocean.name)
        }
        .navigationTitle("海洋")
        .toolbar { EditButton() }
    }
    Text("\(multiSelection.count) 个选中项")
}

注意:在 iOS 和 iPadOS 中,需要在编辑模式下才能进行选择操作。使用 EditButton 或通过环境值 editMode 控制编辑状态。

层级列表

创建多维列表

使用 children 参数创建支持展开/折叠的层级列表:

struct FileItem: Hashable, Identifiable, CustomStringConvertible {
    var id: Self { self }
    var name: String
    var children: [FileItem]? = nil
    var description: String {
        switch children {
        case nil:
            return "📄 \(name)"
        case .some(let children):
            return children.isEmpty ? "📂 \(name)" : "📁 \(name)"
        }
    }
}

private var fileHierarchyData: [FileItem] = [
    FileItem(name: "users", children: [
        FileItem(name: "user1234", children: [
            FileItem(name: "Photos", children: [
                FileItem(name: "photo001.jpg")
            ]),
            FileItem(name: "Movies", children: [
                FileItem(name: "movie001.mp4")
            ])
        ])
    ]),
    FileItem(name: "private", children: nil)
]

var body: some View {
    List(fileHierarchyData, children: \.children) { item in
        Text(item.description)
    }
}

列表编辑

启用编辑操作

使用 editActions 参数启用删除和移动操作:

@State private var items = ["项目 1", "项目 2", "项目 3"]

var body: some View {
    NavigationView {
        List($items, id: \.self, editActions: [.delete, .move]) { $item in
            Text(item)
        }
        .navigationTitle("可编辑列表")
        .toolbar { EditButton() }
    }
}

自定义删除操作

使用 onDelete 修饰符自定义删除行为:

List {
    ForEach(items, id: \.self) { item in
        Text(item)
    }
    .onDelete { indexSet in
        items.remove(atOffsets: indexSet)
    }
}

自定义移动操作

使用 onMove 修饰符自定义移动行为:

List {
    ForEach(items, id: \.self) { item in
        Text(item)
    }
    .onMove { source, destination in
        items.move(fromOffsets: source, toOffset: destination)
    }
}

列表样式

内置样式

SwiftUI 提供多种列表样式,使用 listStyle(_:) 修饰符应用:

// 默认样式(根据平台自动选择)
List { ... }
    .listStyle(.automatic)

// 纯净样式
List { ... }
    .listStyle(.plain)

// 分组样式
List { ... }
    .listStyle(.grouped)

// 内嵌样式(macOS)
List { ... }
    .listStyle(.inset)

// 侧边栏样式
List { ... }
    .listStyle(.sidebar)

// 边框样式(macOS)
List { ... }
    .listStyle(.bordered)

// 椭圆样式(watchOS)
List { ... }
    .listStyle(.elliptical)

// 轮播样式(watchOS)
List { ... }
    .listStyle(.carousel)

交替行背景

在 macOS 上,某些样式支持交替行背景:

List { ... }
    .listStyle(.inset(alternatesRowBackgrounds: true))

List { ... }
    .listStyle(.bordered(alternatesRowBackgrounds: true))

分组内容

使用 Section

使用 Section 将列表内容分组:

List {
    Section(header: Text("水果")) {
        Text("苹果")
        Text("香蕉")
    }
    
    Section(header: Text("蔬菜")) {
        Text("胡萝卜")
        Text("西兰花")
    }
}

带页眉和页脚的分组

List {
    Section {
        Text("项目 1")
        Text("项目 2")
    } header: {
        Text("页眉")
    } footer: {
        Text("页脚说明文字")
    }
}

常用修饰符

行外观定制

// 自定义行背景
List(items, id: \.self) { item in
    Text(item)
        .listRowBackground(Color.blue.opacity(0.1))
}

// 隐藏行分隔符
List(items, id: \.self) { item in
    Text(item)
        .listRowSeparator(.hidden)
}

// 自定义行分隔符颜色
List(items, id: \.self) { item in
    Text(item)
        .listRowSeparatorTint(.red)
}

// 自定义行内边距
List(items, id: \.self) { item in
    Text(item)
        .listRowInsets(EdgeInsets(top: 10, leading: 20, bottom: 10, trailing: 20))
}

列表级别定制

// 自定义列表行背景
List { ... }
    .listRowBackground(Color.clear)

// 隐藏滚动指示器
List { ... }
    .scrollIndicators(.hidden)

// 控制滚动禁用
List { ... }
    .scrollDisabled(true)

性能优化

使用 LazyVStack vs List

对于简单场景,List 已经进行了懒加载优化。但在某些情况下,LazyVStack 可能提供更多控制:

// List 自动懒加载
List(0..<1000) { index in
    Text("行 \(index)")
}

// 显式使用 LazyVStack
ScrollView {
    LazyVStack {
        ForEach(0..<1000) { index in
            Text("行 \(index)")
        }
    }
}

标识符选择

确保为列表项提供稳定且唯一的标识符,避免使用数组索引作为 ID:

// ❌ 不推荐:使用索引
List(items.indices, id: \.self) { index in
    Text(items[index])
}

// ✅ 推荐:使用稳定的唯一标识符
List(items, id: \.id) { item in
    Text(item.name)
}

平台差异

iOS/iPadOS

  • 默认样式为 .insetGrouped
  • 选择需要编辑模式
  • 支持滑动删除

macOS

  • 默认样式为 .inset
  • 支持直接点击选择
  • 支持交替行背景
  • 支持边框样式

watchOS

  • 提供 .elliptical.carousel 特殊样式
  • 自动适应圆形表盘

tvOS

  • 优化焦点导航
  • 支持遥控器交互

最佳实践

  1. 使用 Identifiable:让数据模型遵循 Identifiable 协议,简化列表创建
  2. 避免过度嵌套:保持列表行视图简洁,复杂视图提取为独立组件
  3. 合理使用 Section:通过分组提高可读性
  4. 选择合适的样式:根据平台和设计需求选择列表样式
  5. 性能考虑:对于大数据集,确保使用稳定的标识符
  6. 编辑模式:在 iOS 上记得提供进入编辑模式的方式(如 EditButton

总结

List 是 SwiftUI 中功能强大且灵活的列表组件,支持静态内容、数据驱动、选择、层级结构和编辑等多种场景。通过合理使用样式和修饰符,可以创建符合平台规范且用户体验优秀的列表界面。

在 GitHub 上编辑

上次更新于