简体   繁体   中英

Unable to initialize @StateObject ViewModel in SwiftUI

I am trying to inject the managedObjectContext in ViewModel and for some weird reason it is throwing a weird error.

struct ContentView: View {
   
    @Environment(\.managedObjectContext) var viewContext
    @StateObject var addBudgetVM: AddBudgetViewModel
    
    init() {
        // THIS LINE CAUSES ISSUES 
        addBudgetVM = AddBudgetViewModel(context: viewContext)
    }
    
    var body: some View {
       // some code here
    }
    
}

Cannot assign to property: 'addBudgetVM' is a get-only property

Here is the implementation of AddBudgetViewModel

import Foundation
import CoreData

class AddBudgetViewModel: ObservableObject {
    
    @Published var name: String = ""
    var context: NSManagedObjectContext
    
    init(context: NSManagedObjectContext) {
        self.context = context
    }
    
    func save() {
        
    }
    
}

You could initialize the view model without the parameter and inside the view model declare de environment

struct ContentView: View {
@StateObject var addBudgetVM: AddBudgetViewModel = AddBudgetViewModel()

  var body: some View {
   // some code here
}

}

The viewModel:

class AddBudgetViewModel: ObservableObject {

@Published var name: String = ""
@Environment(\.managedObjectContext) var viewContext
    
func save() {
    
}

}

I find the best approach is to inject the view model from the superview. This nicely separates the view, which consumes the view model, from the creation of the view model. It improves testability as it makes it easy to inject a mock view model.

Something like:


struct ParentView: View {
      @Environment(\.managedObjectContext) var viewContext

      var body: some View {
          //...
          AddBudgetView(viewModel: AddBudgetViewModel(context: viewContext))
          //...
      }
}


struct AddBudgetView: View {

      viewModel: AddBudgetViewModel 

      var body: some View {
          // ...
      }
}

class AddBudgetViewModel: ObservableObject {

    private (set) var context: NSManagedObjectContext

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

   func save() {
    
   }
}

I probably wouldn't even have Core Data code in the view model. I would put all of that in a main model and pass the main model instance to the view model initialiser instead of the managed object context. The view model can then call methods on the main model to create and save items. This way your view model is isolated from the storage implementation detail and you could make changes to your store, such as replacing Core Data, without having to touch view models all over the place.

You need to instantiate the StateObject like this:

 _addBudgetVM = StateObject(wrappedValue: AddBudgetViewModel(context: viewContext))

Although Apple recommends not doing this .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM