SwiftUI报错:未找到ShortcutsViewModel类型的ObservableObject
重构代码时,原本存在多个不相关的ShortcutsViewModel实例,尝试统一规范处理后遇到致命错误:
Thread 1: Fatal error: No ObservableObject of type ShortcutsViewModel found. A View.environmentObject(_:) for ShortcutsViewModel may be missing as an ancestor of this view.
补充说明:ContentView属于个人开发的包,已导入项目中。
相关代码片段
主视图
WidgetContentView( widgetView: WidgetViewShowing( maxOfItemsPerPage: 1, numberOfWidgetRows: 1 ), viewModel: ShortcutsViewModel( widgetNamePrefix: "smallWidget", contract: ShortcutWidgetManager() ) )
创建StateObject的视图
public struct WidgetContentView<WidgetViewShowing: View>: View { private var widgetView: WidgetViewShowing private var total: Int { viewModel.shortcutsViewData?.count ?? 0 } @StateObject public var viewModel: ShortcutsViewModel public init( viewModel: ShortcutsViewModel ) { _viewModel = StateObject(wrappedValue: viewModel) } public var body: some View { ZStack { HStack(content: { widgetView .environmentObject(viewModel) }) } } }
报错视图(使用EnvironmentObject的位置)
public struct WidgetViewShowing: View { @EnvironmentObject var viewModel: ShortcutsViewModel var shortcutsViewDataList: [ShortcutViewData]? { viewModel.shortcutsViewData // 报错发生在此处 } }
核心问题是视图初始化时机不匹配:你在主视图中提前创建了WidgetViewShowing实例并传入WidgetContentView,但此时WidgetContentView的@StateObject viewModel还未完成初始化,更没有将其注入到视图环境中。当WidgetViewShowing被创建时,它会立即尝试读取@EnvironmentObject,但环境中根本不存在对应的ShortcutsViewModel实例,因此触发致命错误。
方案1:通过闭包延迟创建子视图
修改WidgetContentView,让它接收一个创建WidgetViewShowing的闭包,确保子视图在body渲染时才被创建,此时viewModel已经被注入到环境中。
修改后的WidgetContentView代码:
public struct WidgetContentView<WidgetViewShowing: View>: View { private let makeWidgetView: () -> WidgetViewShowing private var total: Int { viewModel.shortcutsViewData?.count ?? 0 } @StateObject public var viewModel: ShortcutsViewModel public init( viewModel: ShortcutsViewModel, @ViewBuilder makeWidgetView: @escaping () -> WidgetViewShowing ) { _viewModel = StateObject(wrappedValue: viewModel) self.makeWidgetView = makeWidgetView } public var body: some View { ZStack { HStack { makeWidgetView() .environmentObject(viewModel) } } } }
主视图调用方式更新为:
WidgetContentView( viewModel: ShortcutsViewModel( widgetNamePrefix: "smallWidget", contract: ShortcutWidgetManager() ) ) { WidgetViewShowing( maxOfItemsPerPage: 1, numberOfWidgetRows: 1 ) }
方案2:直接传递ViewModel(替代EnvironmentObject)
如果不需要依赖环境传递,可以直接将viewModel作为参数传入WidgetViewShowing,避免环境依赖的问题。
修改WidgetViewShowing:
public struct WidgetViewShowing: View { let viewModel: ShortcutsViewModel let maxOfItemsPerPage: Int let numberOfWidgetRows: Int var shortcutsViewDataList: [ShortcutViewData]? { viewModel.shortcutsViewData } public init( maxOfItemsPerPage: Int, numberOfWidgetRows: Int, viewModel: ShortcutsViewModel ) { self.maxOfItemsPerPage = maxOfItemsPerPage self.numberOfWidgetRows = numberOfWidgetRows self.viewModel = viewModel } }
修改WidgetContentView的初始化和body:
public struct WidgetContentView<WidgetViewShowing: View>: View { private let makeWidgetView: (ShortcutsViewModel) -> WidgetViewShowing private var total: Int { viewModel.shortcutsViewData?.count ?? 0 } @StateObject public var viewModel: ShortcutsViewModel public init( viewModel: ShortcutsViewModel, @ViewBuilder makeWidgetView: @escaping (ShortcutsViewModel) -> WidgetViewShowing ) { _viewModel = StateObject(wrappedValue: viewModel) self.makeWidgetView = makeWidgetView } public var body: some View { ZStack { HStack { makeWidgetView(viewModel) } } } }
主视图调用方式:
WidgetContentView( viewModel: ShortcutsViewModel( widgetNamePrefix: "smallWidget", contract: ShortcutWidgetManager() ) ) { viewModel in WidgetViewShowing( maxOfItemsPerPage: 1, numberOfWidgetRows: 1, viewModel: viewModel ) }
内容的提问来源于stack exchange,提问作者izmatlDev

