0

@EnviromentObject is passed down when the body is called, so it doesn't yet exist during the initialization phase of the View struct. Ok, that is clear. The question is, how do you solve the following problem?

struct MyCollectionScreen: View {
        
    // Enviroment
    @EnvironmentObject var viewContext: NSManagedObjectContext
    
    // Internal dependencies
    @ObservedObject private var provider: CoreDataProvider
    
    init() {
      provider = CoreDataProvider(viewContext: viewContext)
    }
}

The previous doesn't compile and throws the error:

'self' used before all stored properties are initialized.

That is because I am trying to use the @EnviromentObject object before it is actually set.

For this, I am trying a couple of things but I am not super happy with any of them

1. Initialize my provider after the init() method

struct MyCollectionScreen: View {

    // Enviroment
    @EnvironmentObject var viewContext: NSManagedObjectContext

    // Internal dependencies
    @ObservedObject private var provider: CoreDataProvider

    var body: some View {
    }.onAppear {
      loadProviders()
    }
    
    mutating func loadProviders() {
      provider = CoreDataProvider(viewContext: viewContext)
    }
}

but that, doesn't compile either and you get the following error within the onAppear block:

Cannot use mutating member on immutable value: 'self' is immutable

2. Pass the viewContext in the init method and forget about Environment objects:

This second solution works, but I don't like having to pass the viewContext to most of my views in the init methods, I kind of liked the Environment object idea.

struct MyCollectionScreen: View {
            
        // Internal dependencies
        @ObservedObject private var provider: CoreDataProvider
        
        init(viewContext: NSManagedObjectContext) {
          provider = CoreDataProvider(viewContext: viewContext)
        }
    }
Tamil Selvan
  • 1,600
  • 1
  • 9
  • 25
Nicolas Yuste
  • 673
  • 9
  • 15
  • If you are using the SwiftUI Life cycle, have you tried having let viewContext = PersistenceController.shared.container.viewContext? – Timmy Jan 17 '22 at 17:35
  • That is one I am using, yes. The problem is I want that to be injected so I can easily change between the preview and the shared instances without having to modify every single place where I use the viewContext. – Nicolas Yuste Jan 17 '22 at 17:47
  • Have you tried using the built-in @FetchRequest instead of a custom object? In SwiftUI the View structs are supposed to be the primary encapsulation mechanism not objects. – malhal Jan 17 '22 at 18:50

1 Answers1

0

You can make viewContext in CoreDataProvider an optional, and then set it onAppear

struct MyCollectionScreen: View {

    @EnvironmentObject var viewContext: NSManagedObjectContext

    @ObservedObject private var provider = CoreDataProvider()

    var body: some View {
      Text("Hello")
        .onAppear {
          provider.viewContext = viewContext
        }
}

Downside is that you'll now have an optional.

I think that what I would do is to create CoreDataProvider at the same time viewContext is created, and pass it as an environment object as well.

EmilioPelaez
  • 18,758
  • 6
  • 46
  • 50