SwiftUI+CoreData:解决@SectionedFetchRequest按月分组时累计金额错误问题
SwiftUI中使用@SectionedFetchRequest按月分组实现逐笔累计余额
问题
使用CoreData的@SectionedFetchRequest将交易数据按.month分组后,无法正确计算逐笔累计余额——所有交易显示的都是当月总额,需要实现逐笔累计计算,且在添加/删除交易时自动更新累计值。
现有交易数据
| Date | Amount | Index |
|---|---|---|
| June , 30, 2023 | +5 | 0 |
| June , 30, 2023 | -10 | 1 |
| June , 28, 2023 | -4 | 2 |
| May , 17, 2023 | +15 | 0 |
| May , 09, 2023 | -2 | 1 |
| April, 19, 2023 | -4 | 0 |
| April, 12, 2023 | +5 | 1 |
当前代码
struct TransactionsDetail: View { @SectionedFetchRequest<String, TransactionEntity>( sectionIdentifier: \.month, sortDescriptors: [SortDescriptor(\.transactionDate, order: .reverse)] ) private var transactionGroupByMonth var body: some View { List { ForEach(Array(transactionGroupByMonth.enumerated()), id: \.offset) { (index, monthDate) in Section { ForEach(monthDate) { item in HStack{ HStack { Text(item.transactionDate, style: .date) Spacer() Text("\(String(format: "%.2f",item.transactionAmount))") Text("\(String(format: "%.2f",transactionGroupByMonth[index].reduce(0) {$0 + $1.transactionAmount}))") } } } } header: { Text(monthDate.id) } } .padding(.vertical, 1) } } }
当前错误结果
所有交易显示当月总额,而非逐笔累计:
| Date | Amount | Accumulated Amount |
|---|---|---|
| June , 30, 2023 | +5 | -9 |
| June , 30, 2023 | -10 | -9 |
| June , 28, 2023 | -4 | -9 |
| May , 17, 2023 | +15 | +13 |
| May , 09, 2023 | -2 | +13 |
| April, 19, 2023 | -4 | +1 |
| April, 12, 2023 | +5 | +1 |
期望结果
逐笔累计计算余额,增删交易时自动更新:
| Date | Amount | Accumulated Amount |
|---|---|---|
| June , 30, 2023 | +5 | +5 |
| June , 30, 2023 | -10 | -5 |
| June , 28, 2023 | -4 | -9 |
| May , 17, 2023 | +15 | +15 |
| May , 09, 2023 | -2 | +13 |
| April, 19, 2023 | -4 | -4 |
| April, 12, 2023 | +5 | +1 |
注:修正了原期望结果中的计算错误,按排序后的逐笔累加逻辑呈现正确值
解决方案
核心是计算当前交易及之前所有交易的累加值,而非整月总额。通过以下步骤实现:
1. 添加前缀和计算扩展
为Collection添加扩展,快速计算前缀和数组(一次计算,多次复用,提升性能):
extension Collection where Element: AdditiveArithmetic { func prefixSums() -> [Element] { var sums = [Element]() var currentSum: Element = .zero for element in self { currentSum += element sums.append(currentSum) } return sums } // 针对交易实体的重载,直接提取金额计算前缀和 func transactionPrefixSums() -> [Double] where Element == TransactionEntity { self.prefixSums { $0.transactionAmount } } }
2. 修改视图代码
在每个月份分组内,先计算该组的前缀和数组,再遍历交易时取对应索引的累计值:
struct TransactionsDetail: View { @SectionedFetchRequest<String, TransactionEntity>( sectionIdentifier: \.month, sortDescriptors: [SortDescriptor(\.transactionDate, order: .reverse)] ) private var transactionGroupByMonth var body: some View { List { // 直接遍历分组,无需enumerated() ForEach(transactionGroupByMonth) { section in let transactions = Array(section) // 预计算当前月份的逐笔累计金额 let accumulatedAmounts = transactions.transactionPrefixSums() Section(header: Text(section.id)) { // 遍历交易时同时拿到索引和实体 ForEach(Array(transactions.enumerated()), id: \.element.id) { idx, item in HStack { Text(item.transactionDate, style: .date) Spacer() Text(String(format: "%.2f", item.transactionAmount)) // 取对应索引的累计值 Text(String(format: "%.2f", accumulatedAmounts[idx])) } } } } .padding(.vertical, 1) } } }
原理说明
- 原代码错误原因:
transactionGroupByMonth[index].reduce(0) { $0 + $1.transactionAmount }是对整个月份分组的所有金额求和,因此每个交易都显示当月总额。 - 修正逻辑:通过
enumerated()拿到交易在分组内的索引,结合前缀和数组,直接取到从分组第一笔到当前交易的累加值。 - 自动更新:由于
@SectionedFetchRequest会监听CoreData数据变化,当增删交易时,分组数据自动刷新,前缀和数组也会重新计算,实现累计值自动更新。
内容的提问来源于stack exchange,提问作者mzkn
相关产品推荐
相关产品推荐

