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

VisionOS应用中SwiftData无法删除收藏餐品问题求助

问题分析与解决方案

你遇到的问题核心是删除时传入的MetaMeal是新创建的实例,并非SwiftData持久化存储中的对象。SwiftData的modelContext.delete(_:)只能删除已经被上下文管理的实例,新创建的MetaMeal未关联到数据库中的数据,所以调用删除不会有任何效果。


修复步骤

1. 修改ViewModel,通过Meal的唯一标识查找对应MetaMeal

在SwiftDataViewModel中添加根据idMeal查找MetaMeal的方法,删除时先定位数据库中的实例再操作:

import Foundation
import SwiftData

@Observable
final class SwiftDataViewModel: ObservableObject {
    let container: ModelContainer
    var likedMeals: Set<Meal> = []
    
    // 改用do-catch处理容器初始化错误,避免try!崩溃
    init() {
        do {
            container = try ModelContainer(for: MetaMeal.self)
            Task { @MainActor in
                getMeals()
            }
        } catch {
            fatalError("ModelContainer初始化失败: \(error)")
        }
    }
    
    @MainActor
    var modelContext: ModelContext {
        container.mainContext
    }
    
    @MainActor
    func getMeals() {
        let fetchDescriptor = FetchDescriptor<MetaMeal>(sortBy: [SortDescriptor<MetaMeal>(\.meal.strMeal)])
        guard let metaMeals = try? modelContext.fetch(fetchDescriptor) else {
            likedMeals = []
            return
        }
        likedMeals = Set(metaMeals.map { $0.meal })
    }

    // 直接接收Meal参数,内部自动判断插入/删除
    @MainActor
    func handleMeal(meal: Meal) {
        isMealFavorite(meal) ? deleteMeal(meal: meal) : insert(meal: meal)
    }

    @MainActor
    func insert(meal: Meal) {
        let metaMeal = MetaMeal(meal: meal)
        modelContext.insert(metaMeal)
        try? modelContext.save()
        getMeals()
    }

    @MainActor
    func deleteMeal(meal: Meal) {
        // 根据idMeal查找数据库中的目标实例
        let fetchDescriptor = FetchDescriptor<MetaMeal>(predicate: #Predicate { $0.meal.idMeal == meal.idMeal })
        guard let targetMeal = try? modelContext.fetch(fetchDescriptor).first else {
            return
        }
        modelContext.delete(targetMeal)
        try? modelContext.save()
        getMeals()
    }
    
    @MainActor
    func isMealFavorite(_ meal: Meal) -> Bool {
        likedMeals.contains(meal)
    }
}

2. 调整View中的按钮逻辑

无需手动创建MetaMeal,直接传入当前餐品实例,同时优化状态同步顺序:

struct MealDetail: View {
    @Environment(\.openWindow) private var openIngredientsWindow
    @State private var isHeartFilled = false
    @Environment(SwiftDataViewModel.self) var swiftDataViewModel
   
    var meal: Meal

    var body: some View {
        // ... 原有视图代码保持不变 ...
        
        HStack {
            Button(action: {
                // 先处理数据,再同步UI状态
                swiftDataViewModel.handleMeal(meal: meal)
                isHeartFilled = swiftDataViewModel.isMealFavorite(meal)
                print(swiftDataViewModel.likedMeals.count)
            }) {
                Label("", systemImage: isHeartFilled ? "heart.fill" : "heart")
            }
            .symbolEffect(.bounce.down, value: isHeartFilled)
            .padding()
            
            // ... 其他按钮代码保持不变 ...
        }
        
        // ... 原有视图代码保持不变 ...
    }
}

额外优化建议

  • 避免强制解包:初始化ModelContainer时用do-catch处理错误,不要依赖try!,防止意外崩溃。
  • 简化状态同步:可以用@Query属性包装器替代手动调用getMeals(),让likedMeals自动监听SwiftData的数据变化。
  • 添加唯一约束:在MetaMeal中为meal.idMeal添加@Attribute(.unique)约束,避免重复插入相同餐品。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 19:13:24