问题描述
在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?")
}