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

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

关键修改点:

  • 把原来的DeleteDialogue Bool变量换成ingredientToDelete: Ingredient?,用来存储要删除的目标条目
  • 点击删除按钮时,把当前条目赋值给ingredientToDelete,触发弹窗
  • 弹窗的isPresented绑定到$ingredientToDelete.isPresent(),通过扩展方法实现可选值到Bool绑定的转换
  • 删除时使用存储的ingredientToDelete对象,确保删的是你选中的条目
  • 删除完成后清空变量,关闭弹窗

内容的提问来源于stack exchange,提问作者Maximilian Henn

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 12:09:20