0

When I select a row in my NavigationView from TeamsView I get this error:

Fatal error: No ObservableObject of type UserManager found. A View.environmentObject(_:) for UserManager may be missing as an ancestor of this view.

UserManager is where I set up my Object

UserManager:

import Foundation
import FirebaseFirestore
import FirebaseFirestoreSwift

struct DBUser {
    let userId: String
    let email : String?
    let photoUrl : String?
    let dateCreated : Date?
}

final class UserManager: ObservableObject {
    @Published var currentUser: DBUser?
    
    static let shared = UserManager()
    private init() {}
    
    func createNewUser(auth: AuthDataResultModel) async throws {
        
        var userData: [String: Any] = [
            "user_id": auth.uid,
            "date_created": Timestamp(),
        ]
        if let email = auth.email {
            userData["email"] = email
        }
        if let photoURL = auth.photoUrl {
            userData["photo_url"] = photoURL
        }
        
        try await Firestore.firestore().collection("Users").document(auth.uid).setData(userData, merge: false)
    }
    
    func getUser(userId: String) async throws -> DBUser {
        let snapshot = try await Firestore.firestore().collection("Users").document(userId).getDocument()
        
        guard let data = snapshot.data(), let userId = data["user_id"] as? String else {
            throw URLError(.badServerResponse)
        }
        let email = data["email"] as? String
        let photoUrl = data["photo_url"] as? String
        let dateCreated = data["date_created"] as? Date
        
        return DBUser(userId: userId, email: email, photoUrl: photoUrl, dateCreated: dateCreated)
    }
    
    func addFavouriteTeamToUser(_ teamName: String, userId: String) {
        Firestore.firestore().collection("Users").document(userId).setData(["favourite_team_name": teamName], merge: true)
    }
}

TeamsView is the View that shows a list of teams in a NavigationView and when I press one the error occurs

TeamsView:

import SwiftUI

struct TeamsView: View {
    
    @State private var showSignInView: Bool = false
    
    @StateObject var vm = TeamsViewModel()
    @State private var query = ""
    
    @EnvironmentObject var userManager: UserManager
    
    var body: some View {
        NavigationView {
            List {
                ForEach(vm.filteredData) { team in
                    NavigationLink(destination: ProfileView(showSignInView: $showSignInView)) {
                        HStack {
                            TeamView(team: team)
                        }
                        .onTapGesture {
                            addUserFavouriteTeam(team)
                        }
                    }
                }
            }
            .navigationTitle("Teams")
            .searchable(text: $query, prompt: "Select your Favourite Team")
            .onChange(of: query) { newQuery in vm.search(with: newQuery)
            }
            .onAppear() {
                vm.search()
            }
            .overlay {
                if vm.filteredData.isEmpty {
                    EmptyView()
                }
            }
        }
    }
    
    private func addUserFavouriteTeam(_ team: Team) {
        if let userId = userManager.currentUser?.userId {
            userManager.addFavouriteTeamToUser(team.name, userId: userId)
        }
    }
}

struct TeamsView_Previews: PreviewProvider {
    static var previews: some View {
        TeamsView()
    }
}

TeamView is the View I'm showing within the NavigationView in TeamsView

TeamView:

struct TeamView: View {

let team: Team
@EnvironmentObject var userManager: UserManager

var body: some View {
    HStack{
        Image(team.name)
            .resizable()
            .aspectRatio(contentMode: .fit)
            .frame(width: 20, height: 20)
        Text(team.name)
    }
}

}

Finally, here's my RootView

RootView:

struct RootView: View {
    
    @State private var showSignInView: Bool = false
    @EnvironmentObject var userManager: UserManager
    
    var body: some View {
        ZStack {
            if !showSignInView {
                NavigationStack {
                    ProfileView(showSignInView: $showSignInView)
                }
            }
        }
        .onAppear() {
            let authUser = try? AuthenticationManager.shared.getAuthenticatedUser()
            self.showSignInView = authUser == nil
        }
        .fullScreenCover(isPresented: $showSignInView) {
            NavigationStack {
                AuthenticationView(showSignInView: $showSignInView).environmentObject(userManager)
            }
            .environmentObject(userManager)
        }
    }
}

struct RootView_Previews: PreviewProvider {
    static var previews: some View {
        let userManager = UserManager.shared
        return RootView().environmentObject(userManager)
    }
}

Update: I've tried reading other people with similar issues and they added .environmentObject(userManager) in their other views but I'm still having the issue. e.g. SwiftUI -> Thread 1: Fatal error: No observable object of type MyObject.Type found (EnvironmentObject in sheet)

  • At some early point in the view hierarchy you have to declare a `@StateObject var userManager = UserManager()` and put it in the environment. Or use the `shared` object, but `EnvironmentObject` is the better choice in SwiftUI. – vadian Jul 17 '23 at 06:40

1 Answers1

0

At some early point in the view hierarchy you have to declare a @StateObject var userManager = UserManager() and put it in the environment. Or use the shared object, but EnvironmentObject is the better choice in SwiftUI.