Search code examples
swiftswiftuiswift-package-manager

Fatal error: No ObservableObject of type Type found


I'm refactoring some code, and happens that i had a bunch of unrelated viewModel instances, and now i'm trying to do this right. but, during the process i'm having the error "Thread 1: Fatal error: No ObservableObject of type ShortcutsViewModel found. A View.environmentObject(_:) for ShortcutsViewModel may be missing as an ancestor of this view." . I'm going to let some pieces of code that I think gonna be useful:

(a detail that i dont know if it's important, is that the contentView is part of a personal package, and I dont know if it has some influence in the error (i'm importing the package in my project))

principal view:

WidgetContentView(
    widgetView: WidgetViewShowing(
        maxOfItemsPerPage: 1,
        numberOfWidgetRows: 1
    ),
    viewModel: ShortcutsViewModel(
        widgetNamePrefix: "smallWidget",
        contract: ShortcutWidgetManager()
    )
)

view that starts 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)
            })
        }
    }
}

view EnvironmentObject that would be using instance from StateObject:

public struct WidgetViewShowing: View {
    @EnvironmentObject var viewModel: ShortcutsViewModel
    
    var shortcutsViewDataList: [ShortcutViewData]? {
        viewModel.shortcutsViewData // THE ERROR IS HAPPENING HERE
    }
}

Solution

  • @StateObject is for loading or saving models and is used like this:

    @StateObject prviate var modelStore = ModelStore()
    

    The reason for this is to ensure it does not passed any dependencies because that would invalidate its use as a source of truth. I.e. it would not know that any values passed in have changed since the first time it was init. E.g. in your code, it would not detect any changes to the supplied widgetNamePrefix.

    Another issue in your code is this memory leak, both ShortcutsViewModel and ShortcutWidgetManager are leaked. You are not supposed to init objects in body like this, only simple values or structs.

    ShortcutsViewModel(
            widgetNamePrefix: "smallWidget",
            contract: ShortcutWidgetManager() // leaked object
        )  // leaked object
    

    To resolve these issues, you'll need to learn @State and @Binding, computed vars, and how to group related dynamic vars in a custom @State struct with mutating func for logic. This is just how SwiftUI works for view data I'm afraid, legacy view model objects just won't work right.