20
votes

Playin' with SwiftUI and Core Data brought me into a curious problem. So the situation is the following:

I have a main view "AppView" and a sub view named "SubView". The SubView view will be opened from the AppView view if I click the plus button in the NavigationTitleBar as popover or sheet.

@Environment(\.managedObjectContext) var managedObjectContext
@State private var modal: Bool = false
...
Button(action: {
        self.modal.toggle()
      }) {
        Image(systemName: "plus")
      }.popover(isPresented: self.$modal){
        SubView()
      }

The SubView view has a little form with two TextField objects to add a forename and a surname. The inputs of this two objects are handled by two separate @State properties. The third object in this form is simple button, which should save a the fore- and surname to an attached Customer Entity for CoreData.

...
@Environment(\.managedObjectContext) var managedObjectContext
...
Button(action: {
  let customerItem = Customer(context: self.managedObjectContext)
  customerItem.foreName = self.forename
  customerItem.surname = self.surname

  do {
    try self.managedObjectContext.save()
  } catch {
    print(error)
  }
}) {
  Text("Speichern")
}

If I try to save the Customer entity this way, I get the error: "nilError", specially: "Unresolved error Error Domain=Foundation._GenericObjCError Code=0 "(null)", [:]" from NSError.

But after figuring out, that when I add .environment(\.managedObjectContext, context) to the SubView() call like so SubView().environment(\.managedObjectContext, context) it's works like a charm.

Does anyone know, why I need to pass the managedObjectContext a second time? I thought, that I just need to pass the managedObjectContext one time to use it in the whole view hierarchy, like in the SceneDelegate.swift:

    // 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 = AppView().environment(\.managedObjectContext, context)

Is it because calling the SubView() this way, the view is not part of the view hierarchy? I don't understand it...

1
I observed same behavior on iOS 13.1. Xcode 11.1Arun Patra
You aren't the first to find this problem, I solved it by passing the context as a parameter. Hopefully Apple will fix it soon.Michael Salmon
As expected this seems to be a bug in the Compiler of Swift/SwiftUI. So Harlan Haskins from Apple gave me the confirmation for that: bugs.swift.org/browse/SR-11607 - So I hope this will be fixed soon. For the quick fix: Passing .environment(\.managedObjectContext, context) to the SubView popover works.lukas_nitaco

1 Answers

36
votes

WOW THIS DROVE ME NUTS! Especially because the errors tells you absolutely no information as to how to fix.

Here is the fix until the bug in Xcode is resolved:

        .navigationBarItems(trailing:
            Button(action: {
                self.add = true
            }, label: {
                Text("Add Todo List")
            }).sheet(isPresented: $add, content: {
                AddTodoListView().environment(\.managedObjectContext, managedObjectContext)
            })
        )

Just add .environment(\.managedObjectContext, managedObjectContext) to your secondary view (a Modal, in this example).