5.5 List 与导航:新树冠成形


5.5 List 与导航:新树冠成形

本节摘要:新枝的最后一节长出完整树冠:List 是 SwiftUI 对 UITableView 的声明式回应——不用数据源问答协议,数据集合直接长成行;滑动动作、删除、下拉刷新都是一行修饰符。NavigationStack 接管主干,跳转从"手动压栈"变成"声明目的地"。本节合成 SwiftUI 版阳台花园的列表加详情加表单,与第 3、4 章的 UIKit 版逐项对照收束本章。

新树冠:列表加导航

第 4 章实现表格列表写了多长你还记得:注册、数据源两问、委托数问、复用填装、手动 reload。SwiftUI 版一口气写完:

struct GardenListView: View { @StateObject private var store = PlantStore() @State private var showingAddSheet = false var body: some View { NavigationStack { // 主干(对偶 3.6 的 UINavigationController) List { // 列表(对偶 UITableView) ForEach(store.plants) { plant in // 数据直接长成行(Identifiable 见下) NavigationLink(value: plant) { // 行即入口:点击前往该植物的详情 PlantRow(plant: plant) } .swipeActions { // 左滑动作:一行修饰符(4.1 写了十几行) Button("已浇水") { store.water(plant) }.tint(.blue) Button("删除", role: .destructive) { store.remove(plant) } } } } .navigationTitle("阳台花园") // 导航栏标题:一行 .toolbar { // 右上角加号 Button { showingAddSheet = true } label: { Image(systemName: "plus") } } .sheet(isPresented: $showingAddSheet) { // 模态:一行(3.6 手动 present 的对照) AddPlantSheet(store: store) } .refreshable { // 下拉刷新:一行 await store.reloadFromStorage() } } } }

ForEach 能"认行"靠的是可标识协议——差量更新(5.1 第三步)要知道谁是谁,行数据的身份由 id 提供:

struct Plant: Identifiable { // 第 2 章模型的 SwiftUI 补丁:给身份 let id = UUID() // 通用唯一标识 var name: String var kind: PlantKind var passedDays: Int // ...其余同前 } // 效果:删除一株后 ForEach 按 id 找差异,只移除那一行(带默认动画) // 若不给身份:ForEach(plants, id: \.name) 也能跑,但重名植物会错乱——身份要选真正唯一的

导航目的地在树冠外面声明一次,主干自动管理压栈与返回:

// 续在 NavigationStack 闭包内:声明"值 → 页面"的对应关系 .navigationDestination(for: Plant.self) { plant in PlantPageView(plant: plant) // 5.4 排好的详情页,现成挂上 } // 也可以用老式的按路径压栈(对照 3.6 的 pushViewController): // NavigationStack(path: $path) ... path.append(plant) // 声明式的默认选择是 value 式:描述"去哪",不描述"怎么压"

图 5-5 SwiftUI 版阳台花园树冠全景

图 5-5 SwiftUI 版阳台花园树冠全景

两个世界的逐项对照

合成完毕,把第 3、4 章与本章的实现并排放:

任务 UIKit(第 3、4 章) SwiftUI(本章)
列表数据绑定 数据源问答协议答两问 ForEach 数据集合直接展开
行点击跳转 didSelectRowAt 里手动 push NavigationLink 声明入口
左滑操作 委托方法配 UIContextualAction swipeActions 修饰符
下拉刷新 刷新控件加目标方法 refreshable 一行
模态表单 present 加导航包装 sheet 修饰符绑定布尔
数据变了之后 手动 reload(忘了就灵异) 自动差量更新
行身份 IndexPath 行号 Identifiable 的 id

最后一行的差别最深:IndexPath 是位置坐标,数据重排后坐标全变;id 是身份,数据怎么动框架都认得——这也是插入删除自带动画的原因。

案例:迁移"左滑标记已浇水"的完整过程

背景:4.1 在 UIKit 版实现了左滑浇水(十几行),现在搬到 SwiftUI 版。操作:如上 GardenListView 里 swipeActions 一行完成,但有个新需求——浇水后这行要立刻变绿点。结果:直接可用,无任何刷新代码。解读:swipeActions 的闭包调 store.water,@Published 广播、ForEach 差量、PlantRow 重算,三次接力全在 5.3 的水路上;对照 UIKit 版里"改模型加 reloadRows"两步缺一不可,这里的刷新代码不存在也就忘不了。变式:要加"撤销"提示,用提示修饰符包一层确认对话框;要按品种分区列表,ForEach 外再套一层 Section——结构随数据形状走,代码总量几乎不变。

💡 关键直觉:SwiftUI 版代码量少不是语法糖,是职责转移——数据源、复用、reload、压栈这些你曾经手写的同步代码,如今都在框架里跑。你省下的不是打字,是漏改同步的心智负担。

本节要点回顾

  • List 加 ForEach:数据集合直接长成行,行身份靠 Identifiable 的 id,不是位置;
  • 滑动、刷新、模态全是修饰符:swipeActions、refreshable、sheet 各一行;
  • NavigationStack 声明目的地:value 式跳转描述"去哪",返回键免费;
  • IndexPath 换 id:身份驱动让重排与动画天然正确;
  • 本章收束:列表、详情、表单、导航全部声明式重写——与 UIKit 版同构,代码更短、同步负担归零。

新枝已成冠。最后一章把两棵树合进一个园子:综合实战、排错手册与选型。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U