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
相关产品推荐
相关产品推荐

