Recommended method of passing NSManagedObjectContext to @ObservedObject/@StateObject

I have a view that is pulling the context from the environment using the latest SwiftUI CoreData template (i.e. there is a preview/in-memory context vs the persistent context).

I also have an @ObservableObject class that is fetching objects based on the predicates passed in (think dynamic filtering). A random element from these fetched results are then displayed back in the view (i.e. I do not need this ObservableObject class to be a view itself).

However, there is an interesting "issue" where I cannot instantiate my @ObservedObject because the property initializers are run before "self" is available and I need to pass it the NSManagedObjectContext.

The only way I can think to get around this is to create the ObservableObject class outside of the view and pass it in the view's initializer. However, this isn't completely desirable as I would prefer this data be completely private to the view as other views do not need to know about its existence.

I also need it to be an ObserableObject so that the filters can change and it be reflected back in the view observing it.

Am I using the wrong tool or thinking about this wrong?

Code Block swift
class Filter: ObservableObject {
@Published var someObjects: [Objects] = []
/* Need to instantiate with the context so objects can be fetched */
private let context: NSManagedObjectContext
}
struct ContentView: View {
    @Environment(\.managedObjectContext) var context
/* Cannot initialize here as context isn't available */
    @ObservedObject var filter = Filter(context: context)
}


Accepted Reply

You may have already tried, but you can create a SubContentView including all the parts depending on filter.
Code Block
struct SubContentView: View {
@ObservedObject var filter: Filter
var body: some View {
Text("Hello, World!")
//All the parts depending on `filter`.
//...
}
}
struct ContentView: View {
@Environment(\.managedObjectContext) var context
var body: some View {
SubContentView(filter: Filter(context: context))
}
}


Not sure if there are some better ways.

Replies

You may have already tried, but you can create a SubContentView including all the parts depending on filter.
Code Block
struct SubContentView: View {
@ObservedObject var filter: Filter
var body: some View {
Text("Hello, World!")
//All the parts depending on `filter`.
//...
}
}
struct ContentView: View {
@Environment(\.managedObjectContext) var context
var body: some View {
SubContentView(filter: Filter(context: context))
}
}


Not sure if there are some better ways.
hi,

how about making private let context: NSManagedObjectContext be, instead, private var context: NSManagedObjectContext? = nil, writing @ObservedObject var filter = Filter() in the ContentView, and then adding an .onAppear() modifier in the body of the ContentView that does something like

Code Block
var body: some View {
List(filter.someObjects) { object in
// whatever you display here for an object
}
.onAppear(filter.setContext(context))
}

where setContext() is a method on Filter that sets the context and does whatever fetch you need. that should cause the ContentView to update.

hope that helps,
DMG
I really like this approach as it seems to be the cleanest. I wonder if this is an area SwiftUI can improve upon by flattening the need for an internal subview or if it is by design

Another way to do that is to pass the context from the view to the ObservableObject whenever needed (with every function call). This will make you easily create unit tests for the view model (the observable object) by testing its functions using a mock context.