SwiftUI 在 Xcode 预览修改视图 FetchedResults 对象的属性时为什么会崩溃?

概览

从 SwiftUI 诞生那天起,让秃头码农们又爱又恨的 Xcode 预览就在界面调试中扮演了及其重要的角色。不过,就是这位撸码中的绝对主角却尝尝在关键时刻“掉链子”。

比如当修改 SwiftUI 视图中 FetchedResults 对象的属性时,Xcode 预览可能会毫不留情的发生崩溃。这是怎么回事,又该如何解决呢?

在本篇博文中,您将学到如下内容:

  1. 莫名其妙的崩溃场景
  2. 崩溃的根本原因

本篇博文代码测试环境:macOS 15.2 + Xcode 16.1

相信学完本课后,小伙伴们对于 Xcode 预览中 SwiftUI 视图 FetchedResults 对象的领悟又会更加精进一层!

那还等什么呢?Let‘s go!!!;)


1. 莫名其妙的崩溃场景

在 SwiftUI 与 CoreData 共同协作的视图中,我们常常需要使用 @FetchRequest 属性包装器来获取所有相关的托管对象:

struct MainTest: View {    
    @FetchRequest(sortDescriptors: [.init(keyPath: \WorryObject.name, ascending: false)], animation: .bouncy) var allWorryObjects: FetchedResults<WorryObject>
    
    @State var ascending = false
    
    var body: some View {
        NavigationStack {
            List {
                ForEach(allWorryObjects) { wObj in
                    VStack(alignment: .leading) {
                        Label(wObj.name ?? "", systemImage: "brain.filled.head.profile")
                            .font(.title2)
                        Text(wObj.desc ?? "")
                            .foregroundStyle(.gray)
                    }
                }
            }
            .toolbar {
                ToolbarItem(placement: .topBarLeading) {
                    Button(ascending ? "升序" : "降序") {
                        ascending.toggle()
                    }
                }
            }
            .onChange(of: ascending) {_,new in
                allWorryObjects.nsSortDescriptors = [.init(keyPath: \WorryObject.name, ascending: ascending)]
            }
        }
    }
}

#Preview {
    let context = Common.moc_auto

    MainTest()
        .task {
            try! WorryObject.createDefaults(context)
        }
}

我们在上面的代码中主要做了这样几件事:

  • 使用 @FetchRequest 属性包装器产生结果为 FetchedResults<WorryObject> 的托管对象集合;
  • 遍历每个 WorryObject 对象并在视图中显示它们;
  • 让用户通过 ascending 状态切换 allWorryObjects 集合的排序方式,这是利用动态修改 FetchedResults<WorryObject> 的 nsSortDescriptors 属性来实现的;

不过,上面这段代码在 Xcode 预览中会有两个问题:

  1. 我们的 WorryObject.createDefaults() 方法明明产生了若干默认 WorryObject 对象,但在预览视图中却依旧是一片“空白”;
  2. 当我们切换 allWorryObjects 集合的排序方式时,预览会立即崩溃;

从预览崩溃栈中我们可以发现,崩溃的原因是重新设置 FetchedResults<WorryObject> 对象的 nsSortDescriptors 属性的操作所导致的:

然而奇怪的是,完全相同的代码在模拟器和真机上却毫无任何问题:

为什么单单 Xcode 预览会如此蛋疼呢?

2. 崩溃的根本原因

其实 Xcode Preview 榱崩栋折的原因很简单,我们也在之前很多篇博文详细讨论过了。

简单来说,在 Xcode 预览环境中 SwiftUI 视图若包含 @FetchRequest 属性包装器,则必须向视图传递正确的 managedObjectContext 环境变量。而且该环境变量必须特别针对预览做出修正。

按照这种思路,我们可以很快写出修复方案:

struct MainTest: View {
    
    @Environment(\.managedObjectContext) var context
    @FetchRequest(sortDescriptors: [.init(keyPath: \WorryObject.name, ascending: false)], animation: .bouncy) var allWorryObjects: FetchedResults<WorryObject>
}

#Preview {
    let context = try! Common.moc_auto.cook4Preview(WorryObject.self)
    
    MainTest()
        .environment(\.managedObjectContext, context)
        .task {
            // 创建默认 WorryObject 对象集合
            try! WorryObject.createDefaults(context)
        }
}

在上面的代码中,我们向 MainTest 视图传递了修复后的托管对象上下文,这使得它在 Xcode 预览环境中表现的出类拔萃!棒棒哒!💯

至此,我们已经彻底了解了博文开头那个问题,并一发入魂给出完美的解决方案!么么哒!

总结

在本篇博文中,我们讨论了为何包含 FetchedResults 对象的 SwiftUI 视图属性被修改时,在 Xcode 预览中会导致崩溃。并在最后给出完美解决之道。

感谢观赏,再会啦!8-)

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容