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

SwiftUI Sheet中如何将上下文连接到持久化存储协调器?

问题描述

应用背景

我正在开发一款名为Interval的SwiftUI应用,使用CoreData存储Workout与其Step的一对多关系,每个Workout包含多个Step。

视图层级

  • ContentView -> DetailView -> EditView(以Sheet形式弹出)
  • ContentView展示所有Workout列表,点击条目打开DetailView显示对应Workout的所有Step
  • DetailView中点击工具栏按钮弹出EditView Sheet,用于添加、删除或重新排序当前Workout的Step

遇到的问题

为了让列表的.onDelete、.onMove功能兼容CoreData,我在EditView中用FetchRequest获取当前Workout的所有Step,但打开EditView时会弹出警告:

"环境中的上下文未连接到持久化存储协调器: <NSManagedObjectContext: 0x6000014ccea0>"

原本以为Sheet会自动继承环境的上下文,但实际没有生效,尝试手动在Sheet中传递environment(\.managedObjectContext, moc)也没用。


我的代码

IntervalApp

import SwiftUI

@main
struct IntervalApp: App {
    @StateObject var dataController = DataController()
    
    var body: some Scene {
        WindowGroup {
            NavigationStack {
                ContentView()
                    .environment(\.managedObjectContext, dataController.container.viewContext)
            }
        }
    }
}

DataController

import Foundation
import CoreData
import SwiftUI

class DataController: ObservableObject {
    let container = NSPersistentContainer(name: "Interval")
    
    init() {
        container.loadPersistentStores { description, error in
            if let error = error {
                print("CoreData failed to load: \(error.localizedDescription)")
            }
        }
    }
}

ContentView

import SwiftUI
import CoreData

struct ContentView: View {
    @Environment(\.managedObjectContext) var moc
    @FetchRequest(fetchRequest: Workout.all()) private var workouts
    
    var body: some View {
        List {
            ForEach(workouts) { workout in
                NavigationLink {
                    DetailView(workout: workout)
                } label: {
                    Text(workout.title)
                }
            }
        }
        .navigationTitle("Workouts")
    }
}

DetailView

import SwiftUI

struct DetailView: View {
    @Environment(\.dismiss) var dismiss
    @Environment(\.managedObjectContext) var moc
    @ObservedObject var workout: Workout
    @State private var showSheet: Bool = false
    
    var body: some View {
        List {
            // 展示steps
        }
        .navigationBarTitle(workout.title)
        .toolbar {
            ToolbarItemGroup(placement: .navigationBarTrailing) {
                Button {
                    showSheet.toggle()
                } label: {
                    Label("Edit", systemImage: "square.and.pencil")
                }
            }
        }
        .sheet(isPresented: $showSheet) {
            NavigationStack {
                EditView(workout: workout)
                    // .environment(\.managedObjectContext, moc) 尝试过但无效
            }
        }
    }
}

EditView

import SwiftUI
import CoreData

struct EditView: View {
    @Environment(\.dismiss) var dismiss
    @Environment(\.managedObjectContext) var moc
    @ObservedObject private var workout: Workout
    @FetchRequest private var steps: FetchedResults<Step>
    
    init(workout: Workout) {
        self.workout = workout
        _steps = FetchRequest(
            entity: Step.entity(),
            sortDescriptors: [ NSSortDescriptor(keyPath: \Step.index, ascending: true) ],
            predicate: NSPredicate(format: "workout == %@", workout)
        )
    }
    
    var body: some View {
        List {
            Section {
                ForEach(steps) { step in
                    // 展示step内容
                }
                // .onDelete()
                // .onMove()
            } header: {
                Text("Steps")
            }
        }
        .navigationTitle("Edit workout")
        .toolbar {
            ToolbarItem(placement: .confirmationAction) {
                Button {
                    // 保存操作
                    dismiss()
                } label: {
                    Text("Save")
                }
            }
        }
    }
}

解决方案

问题根源在于EditView初始化时创建FetchRequest的时机过早:此时SwiftUI还未将环境上下文注入到视图中,导致FetchRequest使用了未连接到持久化存储的默认上下文。

解决方法很简单,在创建FetchRequest时,直接传入当前Workout所在的上下文(NSManagedObject自带managedObjectContext属性),而不是依赖环境注入:

修改EditView的init方法:

init(workout: Workout) {
    self.workout = workout
    // 直接使用workout的上下文创建FetchRequest
    guard let moc = workout.managedObjectContext else {
        fatalError("Workout must be attached to a managed context")
    }
    _steps = FetchRequest(
        entity: Step.entity(),
        sortDescriptors: [ NSSortDescriptor(keyPath: \Step.index, ascending: true) ],
        predicate: NSPredicate(format: "workout == %@", workout),
        context: moc // 关键:指定上下文
    )
}

这样修改后,FetchRequest会直接使用Workout所在的有效上下文,无需依赖环境传递,警告会消失,同时.onDelete和.onMove功能也能正常工作。

另外,你也可以移除EditView中@Environment(\.managedObjectContext) var moc这一行,因为现在已经通过Workout获取了上下文。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 00:12:11