I want to get started with Core Data & SwiftUI and therefore created a new watchOS project using the latest Xcode 11.1 GM.
Then, I copied both persistentContainer
& saveContext
from a fresh iOS project (with Core Data enabled), to gain Core Data capabilities.
After that I modified the HostingController
to return AnyView
and set the variable in the environment.
class HostingController: WKHostingController<AnyView> {
override var body: AnyView {
let managedObjectContext = (WKExtension.shared().delegate as! ExtensionDelegate).persistentContainer.viewContext
return AnyView(ContentView().environment(\.managedObjectContext, managedObjectContext))
}
}
Now I can access the context inside the ContentView
, but not in its sub views.
But thats not how it is intended to be? As far as I know, all sub views should inherit its environment from its super views, right?
Right now, to access it inside its sub views I simply set the environment variables again, like this:
NavigationLink(destination: ProjectsView().environment(\.managedObjectContext, managedObjectContext)) {
HStack {
Image(systemName: "folder.fill")
Text("Projects")
}
}
Once I remove the .environment() parameter inside ContentView, the App will crash, because there is no context loaded?!
The error message is Context in environment is not connected to a persistent store coordinator: <NSManagedObjectContext: 0x804795e0>
.
struct ProjectsView: View {
@Environment(\.managedObjectContext) var managedObjectContext
[...]
}
But again, that can't be right? So, whats causing the error here?
I was able to solve this by fixing up HostingController
and guaranteeing the CoreData stack was setup before view construction. First, let's make sure the CoreData stack is ready to go. In ExtensionDelegate
:
class ExtensionDelegate: NSObject, WKExtensionDelegate {
let persistentContainer = NSPersistentContainer(name: "Haha")
func applicationDidFinishLaunching() {
persistentContainer.loadPersistentStores(completionHandler: { (storeDescription, error) in
// handle this
})
}
}
I had trouble when this property was lazy
so I set it up explicitly. If you run into timing issues, make loadPersistentStores
a synchronous call with a semaphore to debug and then figure out how to delay nib instantiation until the closure is called later.
Next, let's fix HostingController
, by making a reference to a constant view context. WKHostingController
is an object, not a struct. So now we have:
class HostingController: WKHostingController<AnyView> {
private(set) var context: NSManagedObjectContext!
override func awake(withContext context: Any?) {
self.context = (WKExtension.shared().delegate as! ExtensionDelegate).persistentContainer.viewContext
}
override var body: AnyView {
return AnyView(ContentView().environment(\.managedObjectContext, context))
}
}
Now, any subviews should have access to the MOC. The following now works for me:
struct ContentView: View {
@Environment(\.managedObjectContext) var moc: NSManagedObjectContext
var body: some View {
VStack {
Text("\(moc)")
SubView()
}
}
}
struct SubView: View {
@Environment(\.managedObjectContext) var moc: NSManagedObjectContext
var body: some View {
Text("\(moc)")
.foregroundColor(.red)
}
}
You should see the address of the MOC in white above and in red below, without calling .environment
on the SubView
.