SwiftUI中onDelete删除错误交易条目问题求助
问题:删除交易条目时移除错误的条目
我有一个按日期排序的交易条目数组,新增和查看功能正常,但删除操作会移除错误的条目(无错误提示)。断点调试发现,传递给onDelete的是排序后数组的偏移量,而非实际存储数组的偏移量。网上找了CoreData/SwiftData的类似示例,但没找到传递正确偏移量的方法。
核心原因
你原本把onDelete绑定到了外层日期分组的ForEach上,导致拿到的索引是日期分组的索引,而非交易条目的索引;同时直接用索引删除时,排序/分组后的数组索引和原存储数组的索引完全不匹配,自然会删错条目。
解决方案
利用Withdrawal的Identifiable特性,通过唯一ID定位原数组中的条目进行删除,同时把onDelete绑定到内层交易条目的ForEach上,确保删除操作针对单个交易条目。
修改后的完整代码
1. ContentView 修改
struct ContentView: View { @Environment(WithdrawalModel.self) private var wdvm let frmt: DateFormatter = { let formatter = DateFormatter() formatter.dateFormat = "MMM dd, yyyy" return formatter }() func bankEntries(for date: String) -> [Withdrawal] { return wdvm.wdArray.filter { frmt.string(from: $0.wdDate) == date } } var uniqueDates: [String] { Array(Set(wdvm.wdArray.map { frmt.string(from: $0.wdDate) })) .sorted { frmt.date(from: $0) ?? Date() < frmt.date(from: $1) ?? Date() } .compactMap { $0 } } @State private var rates: Double = 0.0 var body: some View { NavigationStack { GeometryReader { g in VStack (alignment: .leading) { if !wdvm.wdArray.isEmpty { HStack { Text("Time/Location") .padding(.leading, g.size.width * 0.1) Text("Local/Home") .padding(.leading, g.size.width * 0.104) } List { ForEach(uniqueDates, id: \.self) { dateItem in Section { // 将onDelete绑定到内层交易条目ForEach ForEach(bankEntries(for: dateItem)) { item in RowView(item: item) } .onDelete { offsets in // 获取当前分组内要删除的条目,通过ID匹配原数组删除 let itemsToDelete = offsets.map { bankEntries(for: dateItem)[$0] } itemsToDelete.forEach { wdvm.deleteWithdrawal($0) } } } header: { Text("\(dateItem)") } } } } else { Text("No Withdrawal Entries") .padding(.top, g.size.height * 0.35) } } .toolbar { ToolbarItem(placement: .principal) { Text("ATM Withdrawals") .foregroundStyle(Color.black) } ToolbarItem(placement: .navigationBarTrailing) { NavigationLink { AddWithdrawalView() } label: { Image(systemName: "plus") .imageScale(.large) .symbolRenderingMode(.hierarchical) } } } .toolbarBackground(Color.orange, for: .navigationBar) .toolbarBackground(.visible, for: .navigationBar) .toolbar(.hidden, for: .tabBar) } } } }
2. WithdrawalModel 修改
@Observable final class WithdrawalModel { var wdArray: [Withdrawal] { didSet { if let encoded = try? JSONEncoder().encode(wdArray) { UserDefaults.standard.set(encoded, forKey: StorageKeys.wdBank.rawValue) } } } init() { if let wdArray = UserDefaults.standard.data(forKey: StorageKeys.wdBank.rawValue) { if let decoded = try? JSONDecoder().decode([Withdrawal].self, from: wdArray) { self.wdArray = decoded return } } self.wdArray = [] } func addNewWithdrawal(wdDate: Date, wdCode: String, wdBank: String, wdAmtL: Double, wdAmtH: Double) { let item = Withdrawal(wdDate: wdDate, wdCode: wdCode, wdBank: wdBank, wdAmtL: wdAmtL, wdAmtH: wdAmtH) wdArray.append(item) } // 新增通过ID删除条目的方法 func deleteWithdrawal(_ item: Withdrawal) { guard let index = wdArray.firstIndex(where: { $0.id == item.id }) else { return } wdArray.remove(at: index) } }
3. Withdrawal 模型(无需修改)
struct Withdrawal: Codable, Identifiable { var id: UUID var wdDate: Date var wdCode: String var wdBank: String var wdAmtL: Double var wdAmtH: Double init(id: UUID = UUID(), wdDate: Date, wdCode: String, wdBank: String, wdAmtL: Double, wdAmtH: Double) { self.id = id self.wdDate = wdDate self.wdCode = wdCode self.wdBank = wdBank self.wdAmtL = wdAmtL self.wdAmtH = wdAmtH } }
关键修改说明
- 移动
onDelete绑定位置:从外层日期分组的ForEach移到内层交易条目ForEach,确保删除操作针对单个交易条目,而非日期分组。 - 通过ID精准删除:利用
Identifiable的id属性匹配原数组中的条目,彻底避免排序/分组导致的索引不匹配问题。 - 封装删除逻辑到ViewModel:符合MVVM架构,View只负责触发删除操作,ViewModel处理具体的数据修改逻辑。
- 修复原删除函数的重复删除错误:原函数中
offsets.forEach内重复调用remove(atOffsets: offsets)会导致多次删除,现在改为单次遍历删除。
内容的提问来源于stack exchange,提问作者Galen Smith
相关产品推荐
相关产品推荐

