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

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
    }
}

关键修改说明

  1. 移动onDelete绑定位置:从外层日期分组的ForEach移到内层交易条目ForEach,确保删除操作针对单个交易条目,而非日期分组。
  2. 通过ID精准删除:利用Identifiable的id属性匹配原数组中的条目,彻底避免排序/分组导致的索引不匹配问题。
  3. 封装删除逻辑到ViewModel:符合MVVM架构,View只负责触发删除操作,ViewModel处理具体的数据修改逻辑。
  4. 修复原删除函数的重复删除错误:原函数中offsets.forEach内重复调用remove(atOffsets: offsets)会导致多次删除,现在改为单次遍历删除。

内容的提问来源于stack exchange,提问作者Galen Smith

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 14:52:33