SwiftData关联关系下撤销/redo异常:我的问题还是框架Bug?
问题:SwiftData 父子关联的撤销/重做功能异常
在SwiftUI macOS应用中使用SwiftData的自动撤销/重做时,遇到以下问题:
- 父实体的增删操作撤销/重做正常
- 子实体插入操作的撤销正常(需先将对象添加到model context再设置父关联)
- 子实体插入的重做或子实体删除的撤销无法生效,子实体不会重新显示
移除删除规则、删除前将parent设为nil均无效果。
复现代码
import SwiftUI import SwiftData @main struct TestApp: App { var body: some Scene { WindowGroup { ContentView() } .modelContainer(for: Parent.self, isUndoEnabled: true) } } @Model final class Parent { @Attribute(.unique) var timestamp = Date.now @Relationship(deleteRule: .cascade, inverse: \Child.parent) var children = [Child]() init() {} } @Model final class Child { @Attribute(.unique) var timestamp = Date.now var parent: Parent? init() {} } struct ContentView: View { @Environment(\.modelContext) private var modelContext @Query private var parents: [Parent] var body: some View { List { ForEach(parents.sorted(by: { $0.timestamp < $1.timestamp })) { parent in HStack { Text("Parent \(parent.timestamp.description)") Button("Add Child") { let child = Child() modelContext.insert(child) child.parent = parent } Button("Delete") { modelContext.delete(parent) } } ForEach(parent.children.sorted(by: { $0.timestamp < $1.timestamp })) { child in HStack { Text("Child \(child.timestamp.description)") Button("Delete") { modelContext.delete(child) } } } .padding([.leading], 20) } } .toolbar { Button("Add Parent") { modelContext.insert(Parent()) } } } }
问题分析与解决方案
核心原因
直接设置child.parent = parent的方式,虽然会自动更新parent.children数组,但SwiftData的撤销追踪系统可能没有完整记录双向关联的变更。导致重做或撤销删除时,仅恢复了Child对象的存在,但parent.children数组没有同步更新,视图因依赖parent.children渲染,所以看不到恢复的子实体。
修复方案
修改子实体的添加逻辑,通过父实体的关联集合来添加子对象,而不是直接设置子对象的parent属性:
Button("Add Child") { let child = Child() modelContext.insert(child) parent.children.append(child) // 改为操作父实体的children集合 }
这样修改后,SwiftData会自动处理双向关联(自动设置child.parent = parent),同时完整记录集合变更到撤销栈中,此时撤销/重做操作就能正常同步更新父子双方的关联,视图也能正确显示恢复的子实体。
结论
这大概率是SwiftData在处理单向设置关联属性时的撤销追踪漏洞。使用父实体集合操作的方式可以作为临时解决方案,同时建议向苹果提交Bug报告,因为直接设置子实体关联属性的场景也应被正确支持撤销/重做。
内容的提问来源于stack exchange,提问作者Amar Sagoo
相关产品推荐
相关产品推荐

