Search code examples
swiftuinsmanagedobjectcontext

SwiftUI - passing managed object context to model


Passing a managed object context to a view in SwiftUI is easy enough using an @Environment variable. But getting that same context to the model and view model, not so much. Here is what I tried:

I created a property called context in the view model. In the view, I pass the managed object context lazily. The problem is that now I'm getting an error when I call a view model's method from the view - "Cannot use mutating getter on immutable value: 'self' is immutable". The method worked before I added the context, so why did it stop working? And, more importantly, how do I make it work?!

Model:

struct Model {

   //use fetch request to get users

   func checkLogin(username: String, password: String) {
      for user in users { 
        if username == user.userEmail && password == user.password {
            return true
        }
    }
    return false
   }
}

View Model:

class ViewModel {
   var context: NSManagedObjectContext
   private var model = Model()

   init(context: NSManagedObjectContext) {
      self.context = context 
   }

   func checkLogin(username: String, password: String) -> Bool {
      model.checklogin(username: username, password: password)
   }
}

And finally, the View:

struct LoginView: View {
   @Environment(\.managedObjectContext) var moc
   lazy var viewModel = ViewModel(context: moc) 

    //Login form
    
    Button(action: {
       if self.viewModel.checkLogin(username: self.email, password: self.password) { 
       //ERROR: Cannot use mutating getter on immutable value: 'self' is immutable
          //allow login
       }
    }) {
        Text("login")
    }
}

Solution

  • You cannot use lazy var in View, because it is immutable. Here is possible approach to solve your case

    class ViewModel {
       var context: NSManagedObjectContext?
       private var model = Model()
    
       init(context: NSManagedObjectContext? = nil) {
          self.context = context
       }
    
       func checkLogin(username: String, password: String) -> Bool {
          return model.checkLogin(username: username, password: password)
       }
    }
    
    struct LoginView: View {
       @Environment(\.managedObjectContext) var moc
    
       private let viewModel = ViewModel()   // no context yet here, so just default
    
        //Login form
        var body: some View {
            Button(action: {
               if self.viewModel.checkLogin(username: self.email, password: self.password) {
                  //allow login
               }
            }) {
                Text("login")
            }
            .onAppear {
                self.viewModel.context = moc  // << set up context here
            }
    
        }
    }