Core Data计算属性更新后视图未刷新,该如何监听?
问题描述
我有两个Core Data实体:First(与Second为一对一关联)、Second(与First为一对多关联)。
父视图通过@FetchRequest加载First的列表:
@FetchRequest( entity: First.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \First.self, ascending: true)] )
同时通过First的扩展添加计算属性,统计关联的已归档Second数量:
extension First { var archivedSeconds: Int { var count = 0 seconds?.forEach { second in if (second as! Second).archived == true { count += 1 } } return count } }
子视图通过带谓词的@FetchRequest筛选出关联当前First且已归档的Second列表:
@FetchRequest( entity: Second.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \Second.self, ascending: true)], predicate: NSCompoundPredicate(type: NSCompoundPredicate.LogicalType.and, subpredicates: [ NSPredicate(format: "first == %@", first), NSPredicate(format: "archived == %@", NSNumber(value: true))]) )
在子视图修改Second的archived状态并保存后,通过NavigationStack返回父视图,发现父视图中显示统计数的Text("\(first.archivedSeconds)")并未刷新。只有点击TabView标签时才会触发刷新,说明计算属性的值已经更新,但视图没有同步更新。
请问这种情况下该如何监听计算属性以更新视图?
解决方案
方法1:利用Core Data的发布者监听关联对象变化
First作为Core Data托管对象本身遵循ObservableObject,但它的默认通知不会感知关联Second对象的属性变化。可以给First扩展添加自定义发布者,同时监听自身和关联Second的变化,触发视图更新:
import Combine import CoreData extension First { // 简化计算属性写法 var archivedSeconds: Int { seconds?.compactMap { $0 as? Second }.filter { $0.archived }.count ?? 0 } // 创建发布者,监听自身和关联Seconds的变化 var archivedSecondsPublisher: AnyPublisher<Int, Never> { let selfChanges = objectWillChange.map { _ in self.archivedSeconds } let secondsChanges = seconds?.publisher .flatMap { $0.objectWillChange } .map { _ in self.archivedSeconds } ?? Empty().eraseToAnyPublisher() return MergeMany(selfChanges, secondsChanges) .removeDuplicates() .eraseToAnyPublisher() } }
在父视图中订阅这个发布者:
struct FirstRowView: View { let first: First @State private var archivedCount: Int = 0 var body: some View { Text("\(archivedCount)") .onAppear { archivedCount = first.archivedSeconds } .onReceive(first.archivedSecondsPublisher) { newCount in archivedCount = newCount } } }
方法2:使用Core Data派生属性(推荐)
直接在Core Data模型中给First添加派生属性,让Core Data自动维护已归档Second的数量,无需手动处理监听逻辑:
- 打开Core Data模型编辑器,选中
First实体,添加新属性archivedSecondsCount,类型设为Integer 32。 - 在属性的Data Model Inspector中,将
Derivation设为Expression,输入表达式:COUNT(seconds.@unionOfObjects.archived == TRUE) - 确认
First与Second的关联关系配置正确。
之后直接在视图中使用该属性,视图会自动响应变化:
Text("\(first.archivedSecondsCount)")
方法3:强制触发视图刷新(简易方案)
通过修改视图的id值强制刷新,适合简单场景:
struct ParentView: View { @FetchRequest(...) var firsts: FetchedResults<First> @State private var refreshTrigger = 0 var body: some View { List(firsts) { first in NavigationLink(destination: ChildView(first: first, refreshTrigger: $refreshTrigger)) { Text("\(first.archivedSeconds)") } } .id(refreshTrigger) // 通过id变化强制刷新列表 } } struct ChildView: View { let first: First @Binding var refreshTrigger: Int func saveArchivedChange() { // 保存Core Data修改的代码 refreshTrigger += 1 // 触发父视图刷新 } }
内容的提问来源于stack exchange,提问作者meechie
相关产品推荐
相关产品推荐

