SwiftUI中从Sheet删除列表项后同步更新列表的实现方案咨询
我最近在SwiftUI开发中碰到了一个挺常见的场景问题,折腾了好一会儿,想跟大家聊聊我的思路和最终的解决办法:
我的场景是这样的:主视图ContentView里有个列表,数据是通过HTTP请求获取的。列表里的每个条目左滑可以打开一个Sheet,进入ItemEditView编辑页面。在编辑页点击删除按钮时,会发HTTP请求删除条目,成功的话就关闭Sheet,但我一开始不知道怎么同时把这个条目从主列表里也移除掉。
一开始的纠结点
最直观的解决办法其实很简单:做一个共享的ViewModel,把主视图里存列表数据的ContentViewModel和编辑视图里处理删除的EditItemViewModel合并,让两个视图共用这个ViewModel。这样删除请求成功后,直接在ViewModel里把条目从列表中移除就行。
但问题来了!我看到的所有开发建议都在说:每个视图应该有自己独立的ViewModel,不能在多个视图之间共享ViewModel。而且在《100 Days of SwiftUI》里也有类似的场景,那边的方案是让编辑视图接收一个可选的尾随闭包,把移除列表条目的逻辑写在主视图里,再通过闭包传给编辑视图。
可我当时卡壳了:这个删除请求是异步的,我怎么在闭包里判断删除是否成功,进而决定要不要关闭Sheet呢?
最终解决的方案
后来我再试了一次,发现其实没那么复杂——给EditItemView传一个异步的闭包就搞定了!具体是这么做的:
- 在主视图
ContentView里,给Sheet里的ItemEditView传入一个闭包,闭包内部调用主ViewModel的deleteItem方法(这个方法负责发HTTP请求,成功的话就从主列表里移除条目,同时返回布尔值表示是否删除成功)。 - 在
ItemEditView里,点击删除按钮时执行这个闭包,根据返回的布尔值判断是否关闭Sheet。
下面是修改后的核心代码:
ContentView 代码
struct ContentView: View { @State private var model = ContentViewModel() @State private var selectedItem: Item? @State private var showEditSheet = false var body: some View { NavigationStack { List { ForEach(model.items) { item in Text(item.name) .swipeActions { Button("Edit") { selectedItem = item } } } } .onAppear { Task { await model.fetchItems() } } .sheet(item: $selectedItem) { item in NavigationStack { ItemEditView(item: item) { await model.deleteItem(item: item) } } } } } }
ItemEditView 代码
struct ItemEditView: View { @Environment(\.dismiss) private var dismiss @Bindable var item: Item @State private var model = EditItemViewModel() var removal: (() async -> Bool)? = nil var body: some View { // 省略编辑条目的其他功能代码 Button("Delete", role: .destructive) { Task { let deleteSuccess = await removal?() ?? false if deleteSuccess { dismiss() } } } } }
这个方案的优势
这样一来,我既遵循了“每个视图有独立ViewModel”的原则(主视图用ContentViewModel管理列表数据,编辑视图用EditItemViewModel处理编辑相关的其他逻辑),又通过闭包把“更新主列表”的逻辑交还给了主视图(毕竟主视图才是列表数据的拥有者),同时还完美处理了异步删除后的结果判断——只有删除成功时才关闭Sheet并同步更新列表。
内容来源于stack exchange

