SwiftUI中@StateObject修饰的ViewModel注入managedObjectContext时报错
错误原因
@StateObject修饰的属性的包装值(即直接访问的addBudgetVM)在init阶段是只读的,不允许直接赋值,要修改它的初始化值需要操作它的投影属性(带下划线前缀的_addBudgetVM)。@Environment属性的注入时机晚于View的init执行,你在init中访问viewContext时,这个环境变量还没有被系统赋值,拿到的是无效值,就算改对了赋值方式也无法拿到正确的context。
解决方案
方案1:属性声明时直接初始化ViewModel(最简便)
如果你的项目使用Xcode默认生成的Core Data模板,存在全局的PersistenceController单例,直接在属性声明时初始化ViewModel即可,不需要放到init中:
struct ContentView: View { @Environment(\.managedObjectContext) var viewContext // 直接使用全局持久化控制器的context初始化 @StateObject var addBudgetVM = AddBudgetViewModel(context: PersistenceController.shared.container.viewContext) var body: some View { // 原有业务代码 } }
方案2:通过ContentView构造器传入context
如果不想依赖全局单例,可以在创建ContentView的上层位置直接把context作为参数传入,在init内操作投影属性完成初始化:
struct ContentView: View { @Environment(\.managedObjectContext) var viewContext @StateObject var addBudgetVM: AddBudgetViewModel // 接收外部传入的context参数 init(context: NSManagedObjectContext) { // 操作投影属性完成StateObject的初始化 _addBudgetVM = StateObject(wrappedValue: AddBudgetViewModel(context: context)) } var body: some View { // 原有业务代码 } }
在创建ContentView的位置(一般是App入口或者父View)传入context即可:
// 示例:App入口处的调用代码 ContentView(context: persistenceController.container.viewContext) .environment(\.managedObjectContext, persistenceController.container.viewContext)
方案3:延迟给ViewModel注入context
如果必须从环境变量获取context,可以调整ViewModel的实现,在View加载完成后再赋值context:
首先修改AddBudgetViewModel:
class AddBudgetViewModel: ObservableObject { @Published var name: String = "" // 修改为可选类型,支持后续赋值 var context: NSManagedObjectContext? init() {} func save() { // 使用前先解包判断 guard let context = context else { return } // 原有保存逻辑 } }
再调整ContentView的实现:
struct ContentView: View { @Environment(\.managedObjectContext) var viewContext // 初始化时不需要传参 @StateObject var addBudgetVM = AddBudgetViewModel() var body: some View { // 原有业务代码 .onAppear { // View加载完成后环境变量已经注入,此时赋值context addBudgetVM.context = viewContext } } }
内容的提问来源于stack exchange,提问作者Mary Doe
相关产品推荐
相关产品推荐

