11

Context

To performance operation on the Core Data object, the managed object context managedObjectContext is needed. The context is passed into View via the environment variable inside SceneDelegate when the project is generated with the "using Core Data" option checked (see below). A related question is Why does Core Data context object have to be passed via environment variable?

let contentView = MainView().environment(\.managedObjectContext, context)

However, when I try to pass the context into the View Model, it complains the following

Cannot use instance member 'context' within property initializer; property initializers run before 'self' is available

struct MainView: View {
    @Environment(\.managedObjectContext) var context
    
    // Computed property cannot be used because of the property wrapper
    @ObservedObject var viewModel = ViewModel(context: context)
}

class ViewModel: ObservableObject {
    var context: NSManagedObjectContext
}

Adding an init() to initialize the view model inside the view causes a different error which fails the build.

Failed to produce diagnostic for expression; please file a bug report

    init() {
        self.viewModel = ViewModel(context: context)
    }

Question

So how can I use / get / pass the context inside the view model? What's the better way to get a context inside the view model?

XY L
  • 25,431
  • 14
  • 84
  • 143
  • You can for instance create an init() for your view and set it there but honestly, if you are using MVVM should you views have access to a NSManagedObjectContext instance? – Joakim Danielson Sep 18 '20 at 17:06
  • the context reference from the `SceneDelegate` seems the only source, is there another way, I can get a context inside View Model? – XY L Sep 18 '20 at 17:09
  • This might help you: https://stackoverflow.com/q/61571960/8697793 – pawello2222 Sep 18 '20 at 17:12
  • @JoakimDanielson the complaining seems from `context` adding an `init()` does not resolve the error and a computed property cannot be used because the view model has a property wrapper – XY L Sep 18 '20 at 17:22
  • Ok, I missed that but if you indeed are using MVVM then the view model should be injected into rather than created in the view. – Joakim Danielson Sep 18 '20 at 17:35
  • could you please explain to me how to do the injection? – XY L Sep 18 '20 at 17:37
  • 1
    You have a init method that takes a view model as argument in your view instead of instantiating the view model inside the view, this makes your code easier to test and you have weaker coupling between the two. – Joakim Danielson Sep 18 '20 at 19:18

2 Answers2

10

Here is your scenario

let contentView = MainView(context: context)          // << inject
        .environment(\.managedObjectContext, context)

struct MainView: View {
    @Environment(\.managedObjectContext) var context

    @ObservedObject private var viewModel: ViewModel // << declare

    init(context: NSManagedObjectContext) {
        self.viewModel = ViewModel(context: context)   // initialize
    }
}

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Asperi
  • 228,894
  • 20
  • 464
  • 690
  • 3
    The init function get called every time the view is presented. May be this view `MainVeiw` is only presented once, but in general this will cause ViewModel to be initialized more than once. – Tony May 13 '21 at 09:47
  • @Tony. Actually it is a nature of view model - to coexist with view, but if you want to inject dependency from outside, then just mail ViewModel with optional read-write property of context, so latter can be injected via property later, and inject that view model via MainView init arguments as well. DependencyInjection for everything! – Asperi Jul 02 '22 at 17:50
  • Should be `@StateObject` and not `@ObservedObject` since it's created within the view. – VoodooBoot Aug 31 '23 at 10:17
1

The CoreData context can get via the AppDelegate object.

import SwiftUI

class ViewModel: ObservableObject {
    var context: NSManagedObjectContext
    
    init() {
        let app = UIApplication.shared.delegate as! AppDelegate
        self.context = app.persistentContainer.viewContext
    }
}

REFERENCE, https://kavsoft.dev/Swift/CoreData/

XY L
  • 25,431
  • 14
  • 84
  • 143