CoreData y SwiftUI: el contexto en el entorno no está conectado a un coordinador de tienda persistente

10

Estoy tratando de enseñarme a mí mismo Core Data creando una aplicación de gestión de tareas. Mi código funciona bien y la aplicación funciona bien hasta que trato de agregar una nueva asignación a la lista. Me sale este error Thread 1: EXC_BREAKPOINT (code=1, subcode=0x1c25719e8)en la siguiente línea: ForEach(courses, id: \.self) { course in. La consola también tiene este error: Context in environment is not connected to a persistent store coordinator: <NSManagedObjectContext: 0x2823cb3a0>.

Sé muy poco acerca de Core Data y no sé cuál podría ser el problema. He configurado entidades de "Asignación" y "Curso" en el modelo de datos, donde el Curso tiene una relación de uno a muchos con la Asignación. Cada tarea se clasificará en un curso en particular.

Este es el código para la vista que agrega una nueva asignación a la lista:

    struct NewAssignmentView: View {

    @Environment(\.presentationMode) var presentationMode
    @Environment(\.managedObjectContext) var moc
    @FetchRequest(entity: Course.entity(), sortDescriptors: []) var courses: FetchedResults<Course>

    @State var name = ""
    @State var hasDueDate = false
    @State var dueDate = Date()
    @State var course = Course()

    var body: some View {
        NavigationView {
            Form {
                TextField("Assignment Name", text: $name)
                Section {
                    Picker("Course", selection: $course) {
                        ForEach(courses, id: \.self) { course in
                            Text("\(course.name ?? "")").foregroundColor(course.color)
                        }
                    }
                }
                Section {
                    Toggle(isOn: $hasDueDate.animation()) {
                        Text("Due Date")
                    }
                    if hasDueDate {
                        DatePicker(selection: $dueDate, displayedComponents: .date, label: { Text("Set Date:") })
                    }
                }
            }
            .navigationBarTitle("New Assignment", displayMode: .inline)
            .navigationBarItems(leading: Button(action: {
                self.presentationMode.wrappedValue.dismiss()
            }, label: { Text("Cancel") }),
                                trailing: Button(action: {
                                    let newAssignment = Assignment(context: self.moc)
                                    newAssignment.name = self.name
                                    newAssignment.hasDueDate = self.hasDueDate
                                    newAssignment.dueDate = self.dueDate
                                    newAssignment.statusString = Status.incomplete.rawValue
                                    newAssignment.course = self.course
                                    self.presentationMode.wrappedValue.dismiss()
                                }, label: { Text("Add").bold() }))
        }
    }
}

EDITAR: Aquí está el código en AppDelegate que configura el contenedor persistente:

lazy var persistentContainer: NSPersistentCloudKitContainer = {
    let container = NSPersistentCloudKitContainer(name: "test")
    container.loadPersistentStores(completionHandler: { (storeDescription, error) in
        if let error = error as NSError? {
            fatalError("Unresolved error \(error), \(error.userInfo)")
        }
    })
    return container
}()

Y el código en SceneDelegate que configura el entorno:

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
    // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
    // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).

    // Get the managed object context from the shared persistent container.
    let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext

    // Create the SwiftUI view and set the context as the value for the managedObjectContext environment keyPath.
    // Add `@Environment(\.managedObjectContext)` in the views that will need the context.
    let contentView = ContentView().environment(\.managedObjectContext, context)

    // Use a UIHostingController as window root view controller.
    if let windowScene = scene as? UIWindowScene {
        let window = UIWindow(windowScene: windowScene)
        window.rootViewController = UIHostingController(rootView: contentView)
        self.window = window
        window.makeKeyAndVisible()
    }
}
Kevin Olmats
fuente
¿Dónde agrega el contexto del objeto administrado al entorno? ¿Cómo se crea ese contexto de objeto gestionado? Parece que no lo ha conectado con un coordinador de tienda persistente,
Paulw11
Agregué el código donde agrego el moc al entorno en mi publicación original para ti.
Kevin Olmats
@KevinOlmats ¿Me ayudó mi respuesta?
fulvio
Compruebe que ha asignado un contexto a través del entorno.environment(\.managedObjectContext, viewContext)
onmyway133
@ onmyway133 esta es la respuesta correcta
Kevin Olmats

Respuestas:

8

En realidad no estás guardando el contexto. Debería ejecutar lo siguiente:

let newAssignment = Assignment(context: self.moc)
newAssignment.name = self.name
newAssignment.hasDueDate = self.hasDueDate
newAssignment.dueDate = self.dueDate
newAssignment.statusString = Status.incomplete.rawValue
newAssignment.course = self.course

do {
    try self.moc.save()
} catch {
    print(error)
}

También @FetchRequest(...)podría verse así:

@FetchRequest(fetchRequest: CourseItem.getCourseItems()) var courses: FetchedResults<CourseItem>

Puede modificar su CourseItemclase para manejar sortDescriptorslo siguiente:

public class CourseItem: NSManagedObject, Identifiable {
    @NSManaged public var name: String?
    @NSManaged public var dueDate: Date?
    // ...etc
}

extension CourseItem {
    static func getCourseItems() -> NSFetchRequest<CourseItem> {
        let request: NSFetchRequest<CourseItem> = CourseItem.fetchRequest() as! NSFetchRequest<CourseItem>

        let sortDescriptor = NSSortDescriptor(key: "dueDate", ascending: true)

        request.sortDescriptors = [sortDescriptor]

        return request
    }
}

Luego, modificaría su ForEach(...)gusto de la siguiente manera y también puede manejar la eliminación de elementos con bastante facilidad:

ForEach(self.courses) { course in
    // ...
}.onDelete { indexSet in
    let deleteItem = self.courses[indexSet.first!]
    self.moc.delete(deleteItem)

    do {
        try self.moc.save()
    } catch {
        print(error)
    }
}

Una cosa que debe asegurarse es que el "Nombre de clase" esté configurado en "CourseItem", que coincide con la CourseItemclase que creamos anteriormente.

Simplemente haga clic en ENTIDADES en su .xcdatamodeIdarchivo y configure todo en lo siguiente (incluido el Módulo en "Módulo de producto actual" y Codegen en "Manual / Ninguno"):

ingrese la descripción de la imagen aquí

fulvio
fuente