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

比如當(dāng)修改 SwiftUI 視圖中 FetchedResults 對象的屬性時,Xcode 預(yù)覽可能會毫不留情的發(fā)生崩潰。這是怎么回事,又該如何解決呢?
在本篇博文中,您將學(xué)到如下內(nèi)容:
- 莫名其妙的崩潰場景
- 崩潰的根本原因
本篇博文代碼測試環(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ù)覽中會有兩個問題:
- 我們的 WorryObject.createDefaults() 方法明明產(chǎn)生了若干默認(rèn) WorryObject 對象,但在預(yù)覽視圖中卻依舊是一片“空白”;
- 當(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-)