You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 02:45:15