Search code examples
swiftuienvironmentobservedobject

Thread 1: Fatal error: No ObservableObject of type


Video Link Youtube

ConsoleError

Fatal error: No ObservableObject of type UserDefaultsConfig found. A View.environmentObject(_:) for UserDefaultsConfig may be missing as an ancestor of this view.: file SwiftUI, line 0

UserDefaults Config

class UserDefaultsConfig: ObservableObject {
    
    @Published var allFavoriteMovie: [PopularMovie] = []
    let defaults = UserDefaults.standard
    
    func setUserDefaults(value: PopularMovie) {
        let encoder = JSONEncoder()
        if let encoded = try? encoder.encode(value) {
            
            defaults.set(encoded, forKey: "favoriteMovie")
        }
        
        if let savedFavoriteMovie = defaults.object(forKey: "favoriteMovie") as? Data {
            let decoder = JSONDecoder()
            if let loadedFavMovie = try? decoder.decode(PopularMovie.self, from: savedFavoriteMovie) {
                allFavoriteMovie.append(loadedFavMovie)
                print("favMovie: \(allFavoriteMovie)")
            }
        }
    }
}

CustomTabView opens first

CustomTabView

TabView(selection: $selectedTab) {
                
       MainView(searchMovie: SearchMovieVM(), mainVM: MainVM())
                .ignoresSafeArea()
                .tag(TabCategory.movieList)
                
       FavoriteView()
                .ignoresSafeArea()
                .tag(TabCategory.favorite)
            }

I click on MovieCell in MainView and direct it to DetailView.

MainView

        NavigationLink(
            destination: DetailView(movie: .constant(mainVM.popularMovie[movie])),
            label: {
                MovieCell(popularMovie: .constant(mainVM.popularMovie[movie]))
                   .foregroundColor(Color(UIColor.label))
            })

DetailView

When I clicked the button here, setUserDefaults function in userDefaultsConfig was running.

struct DetailView: View {
    ....
    @StateObject var userDefaultsConfig = UserDefaultsConfig()
    var body: some View {
        ZStack {
            .....
            ScrollView(.vertical, showsIndicators: false) {
                .....
                    
                    HStack {
                        Spacer()
                        Button(action: { self.userDefaultsConfig.setUserDefaults(value: movie) }) {
                            Image(systemName: "suit.heart")
                                .imageScale(.large)
                                .foregroundColor(.red)
                        }
                    }
                    
                   ....
                    
                }
                .padding(.horizontal)
                
            }
        }
        .environmentObject(userDefaultsConfig)
    }
}

The movie saved in UserDefaults on the DetailView page will be shown on the Favorites page, but when I open the Favorites page, I get an error.

FavoritesView

struct FavoriteView: View {
    @EnvironmentObject var userDefaultsConfig: UserDefaultsConfig
    var body: some View {
        VStack {
//            ForEach(userDefaultsConfig.allFavoriteMovie, id: \.id) { item in
//                Text(item.title ?? "")
//            }
            Text("")
                .onAppear {
                    print(self.userDefaultsConfig.allFavoriteMovie)
                }
        }
    }
}

Solution

  • You need to place state object in CustomTabView, like

    struct CustomTabView: View {
        ....
        @StateObject var userDefaultsConfig = UserDefaultsConfig()
    
        ...
    
          TabView(selection: $selectedTab) {
                    
              MainView(searchMovie: SearchMovieVM(), mainVM: MainVM())
                    .ignoresSafeArea()
                    .tag(TabCategory.movieList)
                    
              FavoriteView()
                    .ignoresSafeArea()
                    .tag(TabCategory.favorite)
           }
           .environmentObject(userDefaultsConfig)    // << here !!
        ...
    

    and in DetailView just use it as environment object, same as you do in FavoriteView, ie.

    struct DetailView: View {
        @EnvironmentObject var userDefaultsConfig: UserDefaultsConfig
    
        ...
    }