Home > Enterprise >  Swift UI -> Thread 1: Fatal error: No ObservableObject of type DetailPageViewModel found
Swift UI -> Thread 1: Fatal error: No ObservableObject of type DetailPageViewModel found

Time:08-25

I am trying to build an app store mock up using swift UI. The app is supposed to pull data from firebase and display it in a list of cards. After tapping the card, it will show the detail of the card. But when I try to build the code, this error appears.

Thread 1: Fatal error: No ObservableObject of type DetailPageViewModel found.

A View.environmentObject(_:) for DetailPageViewModel may be missing as an ancestor of this view.

Before I added the transition logic, I was using @ObservedObject wrapper on the viewModel object, however the transition logic did not work with @ObservedObject. So I thought I must messed up with the passing of the viewModel. So I change it to the @EnvironmentObject, and added .environmentObject(detailObject) to the top-level view. Then the fatal error appears.

The tab bar view:

struct TabBar: View {
    @Namespace var animation
    @StateObject var detailObject = DetailPageViewModel()
    
    var body: some View {
        ZStack {
            TabView {
                //Home Page
                HomePage(animation: animation)
                    .environmentObject(detailObject)
                    .tabItem({
                        Image(systemName: "house")
                        Text("Home")
                    })
                
                //Search Page
                SearchPage()
                    .tabItem({
                        Image(systemName: "magnifyingglass")
                        Text("Search")
                    })     
            }
            if detailObject.show {
                DetailPage(detailPageViewModel: detailObject, animation: animation)
            }
        }
    }
}

Homepage view:

struct HomePage: View {
    //firebase
    //@ObservedObject var detailPageViewModel = DetailPageViewModel()
    
    var animation: Namespace.ID
    @EnvironmentObject var detailPageViewModel : DetailPageViewModel
    
    var body: some View {
        ScrollView(.vertical, showsIndicators: true){
            Spacer()
            //something something
            LazyVStack (alignment: .leading) {
                ForEach (detailPageViewModel.HomepageList){ recipeDets in
                    HomePageCard(recipeDetail: recipeDets, animation: animation)
                        .onTapGesture {
                            print(detailPageViewModel.show)
                            withAnimation(.interactiveSpring(response: 0.5, dampingFraction: 0.8, blendDuration: 0.8)){
                                detailPageViewModel.selectedRecipe = recipeDets
                                detailPageViewModel.show.toggle()
                            }
                            print(detailPageViewModel.show)
                            
                        }
                }
            }
        }//.background(Color.primary.opacity(0.06).ignoresSafeArea())
    }
    
    init(animation: Namespace.ID) {
        self.animation = animation
        detailPageViewModel.getData() //This is where the error pops up
    }
}

DetailPage View:

struct DetailPage: View {
    @ObservedObject var detailPageViewModel : DetailPageViewModel
    var animation: Namespace.ID
    
    var body: some View {
        ScrollView {
            ZStack {
                LazyImage(url: URL(string: detailPageViewModel.selectedRecipe.fullImagePath), resizingMode: .aspectFill)
                    .aspectRatio(contentMode: .fit)
                    .matchedGeometryEffect(id: detailPageViewModel.selectedRecipe.fullImagePath, in: animation)
                    .ignoresSafeArea()      
            }

        }.ignoresSafeArea(.all, edges: .top)
        .statusBar(hidden: true)
    }
}

DetailPageViewModel:

    
    @Published var selectedRecipe = RecipeDetail(...)
    @Published var show = false
    @Published var HomepageList = [RecipeDetail]()
    
    func getData() {
        let db = Firestore.firestore()
        
        db.collection("Recipe").getDocuments { snapshot, error in
            // check for errors
            if error == nil {
                // No errors
                print("no error")
                if let snapshot = snapshot {
                    //Update the list in main thread
                    DispatchQueue.main.async {
                        // get all the documents and create Recipe list
                        self.HomepageList = snapshot.documents.map { recipeInfo in
                            
                            return RecipeDetail(....)
                            
                        }
                    }
                    
                }
            } else {
                // Handle the error
            }
            
        }
    }
}

Edit: Edit the question format. Edit: Added comment to the faulty line.

CodePudding user response:

Do not use EnvironmentObject in the init(), use it instead in the .onAppear {...}. Try this example code:

struct HomePage: View {
    var animation: Namespace.ID
    @EnvironmentObject var detailPageViewModel : DetailPageViewModel
    
    var body: some View {
        ScrollView(.vertical, showsIndicators: true){
            Spacer()
            //something something
            LazyVStack (alignment: .leading) {
                ForEach (detailPageViewModel.HomepageList){ recipeDets in
                    HomePageCard(recipeDetail: recipeDets, animation: animation)
                        .onTapGesture {
                            print(detailPageViewModel.show)
                            withAnimation(.interactiveSpring(response: 0.5, dampingFraction: 0.8, blendDuration: 0.8)){
                                detailPageViewModel.selectedRecipe = recipeDets
                                detailPageViewModel.show.toggle()
                            }
                            print(detailPageViewModel.show)
                        }
                }
            }
        }//.background(Color.primary.opacity(0.06).ignoresSafeArea())
        .onAppear {
            detailPageViewModel.getData()    // <-- here
        }
    }
    
    init(animation: Namespace.ID) {
        self.animation = animation
    }
}
  • Related