SwiftUI误删ForEach中的SwiftData对象问题
SwiftUI误删ForEach中的SwiftData对象问题

SwiftUI误删ForEach中的SwiftData对象问题

问题描述

在SwiftUI中通过ForEach显示SwiftData,当我尝试使用modelContext.delete(_)删除时,发现存在误删的情况。

当我删除第二个存钱罐的时候,就会误删称第一个或第三个。

SwiftData代码

// 存钱罐列表
ForEach(Array(piggyBank.enumerated()), id: \.offset) { index,item in
    // 每个存钱罐
    VStack { 
        // 删除按钮
        Button(action: {
            deletePrompt.toggle()
        }, label: {
            Text("Delete")
            Spacer()
            Image(systemName: "trash")
        })
    }
    // 删除提示框
    .alert("Delete prompt",isPresented: $deletePrompt){
        Button("Confirm", role: .destructive) {
            // 检查是否要删除的是主存钱罐
            if piggyBank[index].isPrimary {
                // 查找其他的存钱罐并设置为主存钱罐
                DispatchQueue.main.async {
                    do {
                        // 获取所有存钱罐
                        let piggyBanks = try modelContext.fetch(FetchDescriptor<PiggyBank>())
                        // 过滤掉当前要删除的存钱罐
                        let remainingBanks = piggyBanks.filter { $0 != piggyBank[index] }
                        // 如果还有其他存钱罐,设置第一个为主存钱罐
                        if let newPrimary = remainingBanks.first {
                            newPrimary.isPrimary = true
                            print("已将另一个存钱罐设置为主存钱罐:\(newPrimary.name ?? "Unknown")")
                        }
                    } catch {
                        print("获取存钱罐列表失败: \(error)")
                    }
                }
            }
            DispatchQueue.main.async {
                modelContext.delete(piggyBank[index]) // 删除数据
            }
        }
    } message: {
        Text("Are you sure you want to delete this piggy bank?")
    }
}

经过排查发现,执行删除操作是通过 modelContext.delete(piggyBank[index]) 来执行的,而index是通过ForEach循环中的 enumerated() 方法获取的。

在SwiftUI中,ForEach 的 index 是基于当前数据源的顺序生成的。如果数据源发生变化,index会变得不准确。

当我删除一个元素时,数组的索引会发生变化,但ForEach可能没有及时更新,导致后续的删除操作指向错误的元素。

解决方案

新增状态变量,临时存储要删除的存钱罐,而不是依赖index,这样可以避免因索引变化导致的误删问题。

@State private var itemToDelete: PiggyBank? // 新增状态变量,用于存储要删除的存钱罐

当点击删除按钮时,设置要删除的存钱罐。

// 在删除按钮中设置要删除的存钱罐
Button(action: {
    itemToDelete = piggyBank[index]
    deletePrompt.toggle()
}, label: {
    Text("Delete")
    Spacer()
    Image(systemName: "trash")
})

最后,使用 modelContext.delete(itemToDelete)删除数据:

modelContext.delete(itemToDelete) // 删除数据

总结

通过使用临时存储的变量标识要删除的存钱罐,避免依赖索引,可以有效地解决ForEach中的误删问题。

完整代码

@State private var deletePrompt = false
@State private var itemToDelete: PiggyBank? // 新增状态变量,用于存储要删除的存钱罐

// 在删除按钮中设置要删除的存钱罐
Button(action: {
    itemToDelete = piggyBank[index]
    deletePrompt.toggle()
}, label: {
    Text("Delete")
    Spacer()
    Image(systemName: "trash")
})

// 在删除提示框中删除存钱罐
.alert("Delete prompt", isPresented: $deletePrompt) {
    Button("Confirm", role: .destructive) {
        if let itemToDelete = itemToDelete {
            // 检查是否要删除的是主存钱罐
            if itemToDelete.isPrimary {
                // 查找其他的存钱罐并设置为主存钱罐
                DispatchQueue.main.async {
                    do {
                        // 获取所有存钱罐
                        let piggyBanks = try modelContext.fetch(FetchDescriptor<PiggyBank>())
                        
                        // 过滤掉当前要删除的存钱罐
                        let remainingBanks = piggyBanks.filter { $0 != itemToDelete }
                        
                        // 如果还有其他存钱罐,设置第一个为主存钱罐
                        if let newPrimary = remainingBanks.first {
                            newPrimary.isPrimary = true
                            print("已将另一个存钱罐设置为主存钱罐:\(newPrimary.name ?? "Unknown")")
                        }
                    } catch {
                        print("获取存钱罐列表失败: \(error)")
                    }
                }
            }
            DispatchQueue.main.async {
                modelContext.delete(itemToDelete) // 删除数据
            }
            do {
                try modelContext.save() // 提交上下文中的所有更改
            } catch {
                print("保存失败: \(error)")
            }
        }
    }
} message: {
    Text("Are you sure you want to delete this piggy bank?")
}

如果您认为这篇文章给您带来了帮助,您可以在此通过支付宝或者微信打赏网站开放者。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注