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

SwiftUI Table在函数返回数据时不刷新的解决方法求助

解决方法

1. 改用@FetchRequest获取Core Data数据,自动监听变化

直接调用函数返回静态数组的方式,无法让SwiftUI感知Core Data的增删改变化。我们需要用@FetchRequest来绑定数据,它会自动监听Core Data上下文的变更并刷新View。

修改AccountView的初始化逻辑,构建对应的Fetch请求:

struct AccountView: View {
    let account: Account
    let month: Int
    let year: Int
    @State var selectedTransactionEntry: TransactionEntry.ID?
    @FetchRequest private var entries: FetchedResults<TransactionEntry>

    init(account: Account, month: Int, year: Int) {
        self.account = account
        self.month = month
        self.year = year
        
        // 构建当月的日期范围:当月第一天到下月第一天
        let calendar = Calendar.current
        guard let startDate = calendar.date(from: DateComponents(year: year, month: month, day: 1)),
              let endDate = calendar.date(byAdding: .month, value: 1, to: startDate) else {
            fatalError("无效的日期参数")
        }
        
        // 构建筛选条件:指定账户 + 交易日期在当月范围内
        let predicate = NSPredicate(
            format: "account == %@ AND transaction.date >= %@ AND transaction.date < %@",
            account, startDate as CVarArg, endDate as CVarArg
        )
        // 按交易日期升序排序,保证累计余额计算顺序正确
        let sortDescriptor = NSSortDescriptor(keyPath: \TransactionEntry.transaction?.date, ascending: true)
        
        _entries = FetchRequest(
            entity: TransactionEntry.entity(),
            sortDescriptors: [sortDescriptor],
            predicate: predicate
        )
    }
    
    // 剩余代码...
}

2. 用包装结构体计算累计余额,避免修改Core Data实体

原代码直接修改TransactionEntry.total(Core Data实体属性)的做法不合理,累计余额是派生值,不需要持久化到数据库。我们可以创建一个包装结构体,在运行时动态计算累计值:

// 包装结构体,包含原始交易条目和计算后的累计余额
struct TransactionEntryWithTotal: Identifiable {
    let entry: TransactionEntry
    let total: Double
    var id: TransactionEntry.ID { entry.id }
}

在AccountView中添加计算属性,基于@FetchRequest的结果计算每个条目的累计余额:

private var entriesWithTotal: [TransactionEntryWithTotal] {
    // 获取账户当月初始余额
    var runningTotal = fetchBalance(account: account, month: month, year: year)
    return entries.map { entry in
        // 根据收支类型更新累计余额
        if entry.credit {
            runningTotal += entry.amount
        } else {
            runningTotal -= entry.amount
        }
        return TransactionEntryWithTotal(entry: entry, total: runningTotal)
    }
}

// 确保fetchBalance函数可在View中访问(根据实际情况调整作用域)
private func fetchBalance(account: Account, month: Int, year: Int) -> Double {
    // 原fetchBalance的实现逻辑
}

3. 更新Table使用计算后的数据源

将Table的数据源替换为entriesWithTotal,并调整列的内容访问方式:

var body: some View {
    Table(entriesWithTotal, selection: $selectedTransactionEntry) {
        TableColumn("Date") { item in
            Text(DateTool.formatter.string(from: item.entry.transaction!.date!))
        }
        TableColumn("Description") { item in
            Text(item.entry.transaction!.details!)
        }
        TableColumn("Category") { item in
            Text(item.entry.account!.name!)
        }
        TableColumn("Credit") { item in
            if item.entry.credit {
                Text("\(item.entry.amount as NSNumber, formatter: DoubleTool.formatter)")
            }
        }
        TableColumn("Debit") { item in
            if !item.entry.credit {
                Text("\(item.entry.amount as NSNumber, formatter: DoubleTool.formatter)")
            }
        }
        TableColumn("Total") { item in
            Text("\(item.total as NSNumber, formatter: DoubleTool.formatter)")
        }
    }
    .frame(height: 210)
}

4. 移除原fetchTransactionEntry函数

现在@FetchRequest已经负责数据获取和变更监听,原有的fetchTransactionEntry函数可以删除。

关键说明

  • @FetchRequest会自动监听Core Data上下文的变化,当交易条目被增删改时,entries会自动更新,触发entriesWithTotal重新计算,进而刷新Table。
  • 用包装结构体存储累计余额,避免修改Core Data实体,保证原始交易数据的单一来源,防止数据不一致问题。

内容的提问来源于stack exchange,提问作者Jean-Charles

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 17:31:57