CoreData et SwiftUI: le contexte dans l'environnement n'est pas connecté à un coordinateur de magasin persistant

10

J'essaie d'enseigner moi-même les données de base en créant une application de gestion des devoirs. Mon code fonctionne correctement et l'application fonctionne correctement jusqu'à ce que j'essaie d'ajouter une nouvelle affectation à la liste. Je reçois cette erreur Thread 1: EXC_BREAKPOINT (code=1, subcode=0x1c25719e8)sur la ligne suivante: ForEach(courses, id: \.self) { course in. La console a aussi cette erreur: Context in environment is not connected to a persistent store coordinator: <NSManagedObjectContext: 0x2823cb3a0>.

Je sais très peu de choses sur Core Data et je ne sais pas quel pourrait être le problème. J'ai configuré des entités "Affectation" et "Cours" dans le modèle de données, où Cours a une relation un-à-plusieurs avec Affectation. Chaque devoir sera classé dans un cours particulier.

Voici le code de la vue qui ajoute une nouvelle affectation à la liste:

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

EDIT: Voici le code dans AppDelegate qui configure le conteneur persistant:

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

Et le code de SceneDelegate qui configure l'environnement:

    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
la source
Où ajoutez-vous le contexte d'objet géré à l'environnement? Comment ce contexte d'objet géré est-il créé? Il semble que vous ne l'ayez pas connecté avec un coordinateur de magasin persistant,
Paulw11
J'ai ajouté le code où j'ajoute le moc à l'environnement dans mon message d'origine pour vous.
Kevin Olmats
@KevinOlmats Ma réponse a-t-elle aidé?
fulvio
Vérifiez que vous avez attribué un contexte via l'environnement.environment(\.managedObjectContext, viewContext)
onmyway133
@ onmyway133 c'est la bonne réponse
Kevin Olmats

Réponses:

8

Vous ne sauvegardez pas réellement le contexte. Vous devez exécuter les éléments suivants:

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

Vous @FetchRequest(...)pourriez également ressembler à ceci:

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

Vous pouvez modifier votre CourseItemclasse pour gérer sortDescriptorsles éléments suivants:

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

Ensuite, vous pouvez modifier votre ForEach(...)comme ceci et vous pouvez également gérer la suppression d'éléments assez facilement:

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

Vous devez vous assurer que le "Nom de classe" est défini sur "CourseItem", ce qui correspond à la CourseItemclasse que nous avons créée précédemment.

Cliquez simplement sur ENTITIES dans votre .xcdatamodeIdfichier et définissez tout sur les éléments suivants (y compris Module sur "Module de produit actuel" et Codegen sur "Manuel / Aucun"):

entrez la description de l'image ici

fulvio
la source