SwiftUI 在 Xcode 預(yù)覽修改視圖 FetchedResults 對象的屬性時為什么會崩潰?

概覽

從 SwiftUI 誕生那天起,讓禿頭碼農(nóng)們又愛又恨的 Xcode 預(yù)覽就在界面調(diào)試中扮演了及其重要的角色。不過,就是這位擼碼中的絕對主角卻嘗嘗在關(guān)鍵時刻“掉鏈子”。

比如當(dāng)修改 SwiftUI 視圖中 FetchedResults 對象的屬性時,Xcode 預(yù)覽可能會毫不留情的發(fā)生崩潰。這是怎么回事,又該如何解決呢?

在本篇博文中,您將學(xué)到如下內(nèi)容:

  1. 莫名其妙的崩潰場景
  2. 崩潰的根本原因

本篇博文代碼測試環(huán)境:macOS 15.2 + Xcode 16.1

相信學(xué)完本課后,小伙伴們對于 Xcode 預(yù)覽中 SwiftUI 視圖 FetchedResults 對象的領(lǐng)悟又會更加精進一層!

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


1. 莫名其妙的崩潰場景

在 SwiftUI 與 CoreData 共同協(xié)作的視圖中,我們常常需要使用 @FetchRequest 屬性包裝器來獲取所有相關(guān)的托管對象:

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 屬性包裝器產(chǎn)生結(jié)果為 FetchedResults<WorryObject> 的托管對象集合;
  • 遍歷每個 WorryObject 對象并在視圖中顯示它們;
  • 讓用戶通過 ascending 狀態(tài)切換 allWorryObjects 集合的排序方式,這是利用動態(tài)修改 FetchedResults<WorryObject> 的 nsSortDescriptors 屬性來實現(xiàn)的;

不過,上面這段代碼在 Xcode 預(yù)覽中會有兩個問題:

  1. 我們的 WorryObject.createDefaults() 方法明明產(chǎn)生了若干默認(rèn) WorryObject 對象,但在預(yù)覽視圖中卻依舊是一片“空白”;
  2. 當(dāng)我們切換 allWorryObjects 集合的排序方式時,預(yù)覽會立即崩潰;

從預(yù)覽崩潰棧中我們可以發(fā)現(xiàn),崩潰的原因是重新設(shè)置 FetchedResults<WorryObject> 對象的 nsSortDescriptors 屬性的操作所導(dǎo)致的:

然而奇怪的是,完全相同的代碼在模擬器和真機上卻毫無任何問題:

為什么單單 Xcode 預(yù)覽會如此蛋疼呢?

2. 崩潰的根本原因

其實 Xcode Preview 榱崩棟折的原因很簡單,我們也在之前很多篇博文詳細(xì)討論過了。

簡單來說,在 Xcode 預(yù)覽環(huán)境中 SwiftUI 視圖若包含 @FetchRequest 屬性包裝器,則必須向視圖傳遞正確的 managedObjectContext 環(huán)境變量。而且該環(huán)境變量必須特別針對預(yù)覽做出修正。

按照這種思路,我們可以很快寫出修復(fù)方案:

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 {
            // 創(chuàng)建默認(rèn) WorryObject 對象集合
            try! WorryObject.createDefaults(context)
        }
}

在上面的代碼中,我們向 MainTest 視圖傳遞了修復(fù)后的托管對象上下文,這使得它在 Xcode 預(yù)覽環(huán)境中表現(xiàn)的出類拔萃!棒棒噠!??

至此,我們已經(jīng)徹底了解了博文開頭那個問題,并一發(fā)入魂給出完美的解決方案!么么噠!

總結(jié)

在本篇博文中,我們討論了為何包含 FetchedResults 對象的 SwiftUI 視圖屬性被修改時,在 Xcode 預(yù)覽中會導(dǎo)致崩潰。并在最后給出完美解決之道。

感謝觀賞,再會啦!8-)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容