本篇目标

掌握 List 与 ForEach 的使用,理解 NavigationStack 导航结构,学会页面跳转、传值、导航栏标题与工具栏配置,能构建带层级跳转的界面。

一、List 基础

List 用于展示可滚动的行列表,是 iOS 中最常见的界面结构。

List {
    Text("第一行")
    Text("第二行")
    Text("第三行")
}

带分区:

List {
    Section("分组一") {
        Text("A")
        Text("B")
    }

    Section("分组二") {
        Text("C")
        Text("D")
    }
}

设置样式:

List {
    Text("第一行")
    Text("第二行")
}
.listStyle(.insetGrouped)

常用列表样式:

样式

说明

.plain

普通列表

.grouped

分组列表

.insetGrouped

内嵌分组(默认)

.sidebar

侧边栏风格

二、ForEach

ForEach 用于根据集合生成视图。

let fruits = ["苹果", "香蕉", "橘子"]

List {
    ForEach(fruits, id: \.self) { fruit in
        Text(fruit)
    }
}

说明:

  • id: \.self 用于标识每个元素。

  • 集合元素需要可哈希。

  • 可以遍历数组、区间、字典。

遍历区间:

List {
    ForEach(1...5, id: \.self) { number in
        Text("第 \(number) 行")
    }
}

遍历对象数组:

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

let items = [
    Item(name: "项目一"),
    Item(name: "项目二"),
    Item(name: "项目三")
]

List(items) { item in
    Text(item.name)
}

说明:

  • 遵循 Identifiable 协议后,不需要写 id。

  • 推荐用 Identifiable,更简洁。

带索引:

List {
    ForEach(Array(fruits.enumerated()), id: \.offset) { index, fruit in
        Text("\(index + 1). \(fruit)")
    }
}

三、列表行交互

添加点击:

List {
    ForEach(items) { item in
        Button(item.name) {
            print("点击了 \(item.name)")
        }
    }
}

使用 NavigationLink:

List {
    ForEach(items) { item in
        NavigationLink(item.name) {
            DetailView(item: item)
        }
    }
}

删除与移动:

@State private var items = ["A", "B", "C"]

List {
    ForEach(items, id: \.self) { item in
        Text(item)
    }
    .onDelete { indexSet in
        items.remove(atOffsets: indexSet)
    }
    .onMove { from, to in
        items.move(fromOffsets: from, toOffset: to)
    }
}

启用编辑模式:

@State private var editMode: EditMode = .inactive

List {
    // ...
}
.environment(\.editMode, $editMode)

Button("编辑") {
    editMode = editMode == .active ? .inactive : .active
}

四、NavigationStack

NavigationStack 是 iOS 16 引入的新导航容器,替代 NavigationView。

基础结构:

NavigationStack {
    List {
        NavigationLink("详情页") {
            DetailView()
        }
    }
    .navigationTitle("首页")
}

说明:

  • NavigationStack 管理导航栈。

  • NavigationLink 触发跳转。

  • .navigationTitle() 设置标题。

兼容旧版本:

NavigationView {
    // ...
}
  • iOS 16+ 推荐用 NavigationStack。

  • 如需兼容 iOS 15 及以下,用 NavigationView。

五、导航传值

通过初始化传值:

struct DetailView: View {
    let name: String

    var body: some View {
        Text("详情:\(name)")
    }
}

NavigationLink("查看详情") {
    DetailView(name: "Hello")
}

传递对象:

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

NavigationLink(value: item) {
    Text(item.name)
}
.navigationDestination(for: Item.self) { item in
    DetailView(item: item)
}

说明:

  • navigationDestination 根据值类型决定目标视图。

  • 适合列表跳转详情。

六、导航标题与工具栏

设置标题:

NavigationStack {
    Text("内容")
        .navigationTitle("页面标题")
}

标题显示模式:

.navigationBarTitleDisplayMode(.inline)

模式

说明

.automatic

自动

.inline

小标题

.large

大标题(默认)

工具栏:

NavigationStack {
    Text("内容")
        .navigationTitle("页面")
        .toolbar {
            ToolbarItem(placement: .navigationBarTrailing) {
                Button("保存") {
                    // 操作
                }
            }

            ToolbarItem(placement: .navigationBarLeading) {
                Button("取消") {
                    // 操作
                }
            }
        }
}

常用位置:

位置

说明

.navigationBarLeading

导航栏左侧

.navigationBarTrailing

导航栏右侧

.bottomBar

底部

.keyboard

键盘上方

七、导航返回

默认返回按钮:

NavigationStack 自动生成返回按钮。

自定义返回:

@Environment(\.dismiss) var dismiss

Button("返回") {
    dismiss()
}

返回根视图:

@Environment(\.dismiss) var dismiss

或使用 NavigationPath 管理:

@State private var path = NavigationPath()

NavigationStack(path: $path) {
    // ...
}

Button("回到首页") {
    path = NavigationPath()
}

八、NavigationPath 多级跳转

@State private var path: [Item] = []

NavigationStack(path: $path) {
    List(items) { item in
        Button(item.name) {
            path.append(item)
        }
    }
    .navigationDestination(for: Item.self) { item in
        DetailView(item: item)
    }
}

说明:

  • path 管理导航栈。

  • 可以编程式跳转。

  • 可以一次跳转多级。

九、标签页 TabView

底部标签栏用于多个主页面切换。

TabView {
    HomeView()
        .tabItem {
            Label("首页", systemImage: "house")
        }

    SettingsView()
        .tabItem {
            Label("设置", systemImage: "gear")
        }
}

带选中状态:

@State private var selection = 0

TabView(selection: $selection) {
    HomeView()
        .tabItem {
            Label("首页", systemImage: "house")
        }
        .tag(0)

    SettingsView()
        .tabItem {
            Label("设置", systemImage: "gear")
        }
        .tag(1)
}

十、综合示例:列表 → 详情

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

struct ContentView: View {
    let items = [
        Item(name: "项目一", description: "描述一"),
        Item(name: "项目二", description: "描述二"),
        Item(name: "项目三", description: "描述三")
    ]

    var body: some View {
        NavigationStack {
            List(items) { item in
                NavigationLink(value: item) {
                    VStack(alignment: .leading) {
                        Text(item.name)
                            .font(.headline)
                        Text(item.description)
                            .font(.subheadline)
                            .foregroundColor(.gray)
                    }
                }
            }
            .navigationTitle("列表")
            .navigationDestination(for: Item.self) { item in
                DetailView(item: item)
            }
        }
    }
}

struct DetailView: View {
    let item: Item

    var body: some View {
        VStack(spacing: 16) {
            Text(item.name)
                .font(.largeTitle)
            Text(item.description)
                .font(.body)
                .foregroundColor(.gray)
        }
        .padding()
        .navigationTitle(item.name)
        .navigationBarTitleDisplayMode(.inline)
    }
}

十一、避坑指南

  • ForEach 的 id 必须唯一:否则界面可能异常。

  • NavigationLink 嵌套过深:注意导航栈管理。

  • NavigationStack 与 NavigationView 不要混用:同一层级只用一个。

  • navigationTitle 要放在 NavigationStack 内部:否则不生效。

  • 列表行点击区域:用 Button 或 NavigationLink 包住整行。

  • 性能问题:长列表使用 LazyVStack 或 List 自带懒加载。

  • @Environment(\.dismiss):子视图返回用 dismiss,不要手动 pop。

十二、检查清单

  • [ ] 会使用 List 展示数据

  • [ ] 掌握 ForEach 的用法

  • [ ] 能实现列表删除与移动

  • [ ] 会使用 NavigationStack 导航

  • [ ] 能实现页面跳转与传值

  • [ ] 会设置导航标题与工具栏

  • [ ] 会使用 TabView 做标签页

  • [ ] 能构建列表 → 详情的完整流程