SwiftUI+CoreData:ForEach删除错误项原因求助
问题
通过ForEach循环展示Core Data获取的条目,每个条目用NavigationLink呈现,支持滑动或上下文菜单触发删除。但添加confirmationDialog把删除操作移到弹窗后,会出现删错条目的情况(只有选中最后一项时正常);如果不使用确认弹窗,直接在按钮里执行删除就正常,这是什么原因?
代码如下:
import Foundation import SwiftUI struct IngredientsList: View { @Environment(\.managedObjectContext) private var viewContext @FetchRequest(sortDescriptors: []) private var ingredients: FetchedResults<Ingredient> @State private var DeleteDialogue = false var body: some View { VStack{ List{ ForEach(ingredients){ingredients in NavigationLink{ RecipeIngredientsDetailed(ingredients: ingredients, editMode: true, createmode: true) } label: { Text(ingredients.ingredientname ?? "") } .swipeActions(){ Button(role: .destructive){ DeleteDialogue = true } label:{ Text("Delete") } } .contextMenu(){ Button(role: .destructive){ DeleteDialogue = true } label:{ Text("Delete") } } .confirmationDialog("Are you sure?", isPresented: $DeleteDialogue){ Button("Delete Ingredient"){ viewContext.delete(ingredients) do{ try viewContext.save() } catch{ } } } message: { Text("This will remove the Ingredient from all Recipes!") } } } } } }
原因
问题出在所有列表条目共用同一个@State private var DeleteDialogue = false变量。当你点击某个条目的删除按钮时,只是把这个Bool变量设为true,但此时ForEach循环已经完成了所有迭代,ingredients这个循环变量已经指向了列表的最后一个条目。等你在弹窗里确认删除时,代码里的viewContext.delete(ingredients)实际上删的是最后一个条目,而不是你点击的那个——只有当你点的就是最后一个条目时,结果才会符合预期。
解决方案
不要用单个Bool变量控制弹窗,而是用一个可选变量存储要删除的目标条目,这样就能精准定位到你要删的对象:
修改后的代码如下:
import Foundation import SwiftUI struct IngredientsList: View { @Environment(\.managedObjectContext) private var viewContext @FetchRequest(sortDescriptors: []) private var ingredients: FetchedResults<Ingredient> // 用可选变量存储要删除的条目,默认nil表示不弹窗 @State private var ingredientToDelete: Ingredient? var body: some View { VStack{ List{ ForEach(ingredients){ ingredient in NavigationLink{ RecipeIngredientsDetailed(ingredients: ingredient, editMode: true, createmode: true) } label: { Text(ingredient.ingredientname ?? "") } .swipeActions(){ Button(role: .destructive){ // 点击时记录要删除的条目 ingredientToDelete = ingredient } label:{ Text("Delete") } } .contextMenu(){ Button(role: .destructive){ // 点击时记录要删除的条目 ingredientToDelete = ingredient } label:{ Text("Delete") } } // 绑定弹窗的显示状态到变量是否非空 .confirmationDialog("Are you sure?", isPresented: $ingredientToDelete.isPresent()){ Button("Delete Ingredient"){ if let target = ingredientToDelete { viewContext.delete(target) do{ try viewContext.save() } catch{ // 这里可以添加错误处理逻辑,比如打印错误信息 print("删除失败:\(error.localizedDescription)") } // 删除后清空变量,关闭弹窗 ingredientToDelete = nil } } } message: { Text("This will remove the Ingredient from all Recipes!") } } } } } } // 给可选类型扩展一个绑定是否存在的方法 extension Optional { func isPresent() -> Binding<Bool> { Binding( get: { self != nil }, set: { if !$0 { self = nil } } ) } }
关键修改点:
- 把原来的
DeleteDialogueBool变量换成ingredientToDelete: Ingredient?,用来存储要删除的目标条目 - 点击删除按钮时,把当前条目赋值给
ingredientToDelete,触发弹窗 - 弹窗的
isPresented绑定到$ingredientToDelete.isPresent(),通过扩展方法实现可选值到Bool绑定的转换 - 删除时使用存储的
ingredientToDelete对象,确保删的是你选中的条目 - 删除完成后清空变量,关闭弹窗
内容的提问来源于stack exchange,提问作者Maximilian Henn
相关产品推荐
相关产品推荐

