You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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的数量,无需手动处理监听逻辑:

  1. 打开Core Data模型编辑器,选中First实体,添加新属性archivedSecondsCount,类型设为Integer 32。
  2. 在属性的Data Model Inspector中,将Derivation设为Expression,输入表达式:COUNT(seconds.@unionOfObjects.archived == TRUE)
  3. 确认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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 00:45:42