SwiftData结合sheet(item:)插入@Model实例后弹窗自动关闭并重新打开的问题求助
嘿,我之前也遇到过完全一样的问题!这其实是SwiftData的对象ID机制和SwiftUI sheet的item:绑定逻辑冲突导致的,不是你的代码写错了。
具体来说,SwiftData的@Model类默认实现了Identifiable,但它的ID是基于底层的持久化对象ID。当你刚插入新模型时,它还处于临时状态,内存里的临时ID和之后SwiftData完成持久化后生成的正式ID不一样。而@Query会监听所有模型的变化,一旦持久化完成,models数组更新,SwiftUI的状态系统会检测到selectedModel的引用“发生了变化”——其实是同一个对象但ID变了,这就会让sheet先因为旧ID的对象“不存在”而自动关闭,紧接着新ID的对象又触发sheet重新打开,全程不需要用户操作。
给你几个靠谱的 workaround,优先满足你“不修改MyModel类”的需求:
方案1:改用sheet(isPresented:)手动管理弹窗状态
放弃依赖item:的自动Identifiable绑定,换成用Bool值控制弹窗显示,再单独存选中的模型实例。这样完全避开ID变化的问题,也是我最推荐的方案:
修改你的ContentView部分代码:
struct ContentView: View { @Environment(\.modelContext) private var modelContext @Query(sort: \MyModel.name) private var models: [MyModel] // 用Bool控制弹窗显示,单独存选中的模型 @State private var isSheetShowing = false @State private var currentModel: MyModel? var body: some View { NavigationStack { List(models) { model in Button(model.name) { currentModel = model isSheetShowing = true } } .navigationTitle("Models") .toolbar { ToolbarItem(placement: .topBarTrailing) { Button("Add") { let newModel = MyModel(name: "Example") modelContext.insert(newModel) currentModel = newModel isSheetShowing = true } } } // 用isPresented绑定,内部判断当前模型 .sheet(isPresented: $isSheetShowing) { if let model = currentModel { Button("Editing \(model.name)") { isSheetShowing = false } } } } } }
这个方案完全不需要改动MyModel,而且逻辑更稳定,不会受SwiftData的ID更新影响。
方案2:延迟设置selectedModel(临时应急用)
如果不想大改代码结构,可以试试在插入模型后延迟一小段时间再赋值给selectedModel,等SwiftData完成持久化操作。不过这个方案依赖时间延迟,不同设备上可能有差异,不太稳定,只适合临时测试:
Button("Add") { let newModel = MyModel(name: "Example") modelContext.insert(newModel) // 延迟0.2秒再赋值,给SwiftData留足持久化时间 DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { selectedModel = newModel } }
不推荐的方案(需要修改MyModel)
如果之后实在没有其他办法,也可以给MyModel手动实现Identifiable,用自己的唯一标识(比如给name加个UUID后缀,或者单独加个id属性),但这会修改模型类,不符合你的需求,所以只提一下。
亲测方案1是最稳定的,我当时就是用这个解决了问题,你可以试试看!
内容来源于stack exchange

