重新创建视图时,如何使
@FetchRequest
的 nsPredicate
与 View
的 @State
保持同步?重新创建视图时,@State
变量会暂时初始化为结构体初始化程序(内联或在init
中)设置的值,然后持久的@State
值会替换瞬态值。然而,当重新创建视图时,任何 @FetchRequest
的 nsPredicate
都会重新初始化,但不会替换为 @FetchRequest
的持久状态。
重现步骤
TestCalendar2
的 xcdatamodel,其中包含一个名为 Item
的实体,该实体具有名为 Date
的
timestamp
MRE
import SwiftUI
import CoreData
struct ContentView: View {
@FetchRequest(sortDescriptors: [], predicate: NSPredicate.init(value: false))
var items: FetchedResults<Item>
var counter: Int
init(counter: Int) {
print("**** init")
self.counter = counter
}
var body: some View {
let _ = Self._printChanges()
List {
ForEach(items) { item in
Text(item.timestamp!, style: .date)
}
}
.onAppear() {
print("**** onAppear")
let monthYear = Calendar.current.dateComponents([.year, .month], from: Date())
items.nsPredicate = NSCompoundPredicate(andPredicateWithSubpredicates: [
NSPredicate(format: "timestamp >= %@", Calendar.current.date(from: monthYear)! as NSDate),
NSPredicate(format: "timestamp <= %@", Calendar.current.date(byAdding: DateComponents(month: 1, day: -1), to: Calendar.current.date(from: monthYear)!)! as NSDate)
])
}
.onReceive(items.publisher.collect()) { _ in
print("**** onReceive")
print(items)
}
}
}
@main
struct TestCalendarApp: App {
@State var counter = 0
let persistenceController = PersistenceController.shared
var body: some Scene {
WindowGroup {
Button("Recreate View") { print("****** Recreate View"); counter += 1 }
ContentView(counter: counter)
.onAppear() {
addItem()
}
}
.environment(\.managedObjectContext, persistenceController.container.viewContext)
}
func addItem() {
let item = Item(context: persistenceController.container.viewContext)
item.timestamp = Calendar.current.startOfDay(for: Date())
try? persistenceController.container.viewContext.save()
}
}
struct PersistenceController {
static let shared = PersistenceController()
let container: NSPersistentContainer
init() {
container = NSPersistentContainer(name: "TestCalendar2")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
container.viewContext.automaticallyMergesChangesFromParent = true
}
}
输出
**** init
ContentView: @self, @identity, _items, @16, @40 changed.
**** onReceive
FetchedResults<Item>(results: <__NSArray0 0x1e9c9d208>(
)
, controller: <_TtGC7SwiftUI15FetchControllerT_C13TestCalendar24ItemGVS_14FetchedResultsS2___: 0x600002616ac0>)
**** onAppear
ContentView: @40 changed.
**** onReceive
FetchedResults<Item>(results: <__NSArrayI 0x600002624d80>(
<Item: 0x600002148f50> (entity: Item; id: 0xa3d8f0f393928422 <x-coredata:///Item/p1>; data: {
timestamp = "2024-04-07 04:00:00 +0000";
})
)
, controller: <_TtGC7SwiftUI15FetchControllerT_C13TestCalendar24ItemGVS_14FetchedResultsS2___: 0x600002616ac0>)
****** Recreate View
**** init
ContentView: @self changed.
**** onReceive
FetchedResults<Item>(results: <__NSArray0 0x1e9c9d208>(
)
, controller: <_TtGC7SwiftUI15FetchControllerT_C13TestCalendar24ItemGVS_14FetchedResultsS2___: 0x600002616ac0>)
一个简单的方法是在主体的顶部设置谓词,然后每当传入
View
的 let 或 View
中声明的状态发生更改时都会更新谓词,例如
func updatePredicate() {
let monthYear = Calendar.current.dateComponents([.year, .month], from: Date())
items.nsPredicate = NSCompoundPredicate(andPredicateWithSubpredicates: [
NSPredicate(format: "timestamp >= %@", Calendar.current.date(from: monthYear)! as NSDate),
NSPredicate(format: "timestamp <= %@", Calendar.current.date(byAdding: DateComponents(month: 1, day: -1), to: Calendar.current.date(from: monthYear)!)! as NSDate)
])
}
var body: some View {
let _ = Self._printChanges()
let _ = updatePredicate()
...