简体   繁体   中英

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

I'm building my first app on IOS using SwiftUI and Firebase for authentication and storage

For login i use the default Firebase UI which is customized through a subclass of the FUIAuthPickerViewController called MyFUIAuthPickerViewController as detailed in https://firebase.google.com/docs/auth/ios/firebaseui

The defaultUI is initialized and shown in the scene delegate file.

// Create the SwiftUI view that provides the window contents.
        //let contentView = ContentView()
        
        self.authUI = _createAuthUI()
        
        guard self.authUI != nil else {
            print("No authUI")
            return
        }
        
        self.authUI?.delegate = self
        
        self.authUI?.shouldHideCancelButton = true
        
        // Use a UIHostingController as window root view controller.
        if let windowScene = scene as? UIWindowScene {
            let window = UIWindow(windowScene: windowScene)
            //window.rootViewController = UIHostingController(rootView: contentView)
            window.rootViewController = UINavigationController(rootViewController: MyFUIAuthPickerViewController(authUI: authUI!))
            self.window = window
            window.makeKeyAndVisible()
        }

The MyFUIAuthPickerViewController subclass contains not a lot at the moment but will be used to add a default background to the authorization screen

import Foundation
import FirebaseUI
import Firebase

class MyFUIAuthPickerViewController: FUIAuthPickerViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
    }
}

For tracking if a user is logged in i use an Observable object called Sessionstore. The code i adapted from https://benmcmahen.com/authentication-with-swiftui-and-firebase/ which was using the old style Bindable protocol

import Foundation
import SwiftUI
import Firebase
import Combine

class SessionStore : ObservableObject
{
    
    @Published var user: AppUser?
    var handle: AuthStateDidChangeListenerHandle?

    func listen () {
           // monitor authentication changes using firebase
           handle = Auth.auth().addStateDidChangeListener { (auth, user) in
               if let user = user {
                   // if we have a user, create a new user model
                   print("Got user: \(user) \(user.displayName!)")
                self.user = AppUser(uid: user.uid,displayName: user.displayName, email: user.email)
               } else {
                   // if we don't have a user, set our session to nil
                   self.user = nil
               }
           }
       }
    
   func signOut () -> Bool {
        do {
            try Auth.auth().signOut()
            print("signed out")
            self.user = nil
            print("user object set to nil")
            return true
        } catch {
            print("Problem encountered signing the user out")
            return false
        }
    }
}

The environment object is present on my contentview and my scenedelegate

scenedelegate

class SceneDelegate: UIResponder, UIWindowSceneDelegate, FUIAuthDelegate{

    var window: UIWindow?
    var authUI: FUIAuth?
    @EnvironmentObject var appSession: SessionStore

contentview

import SwiftUI
import FirebaseUI
struct ContentView: View {

    @EnvironmentObject var session: SessionStore
    
        var body: some View {
            Group{
                if session.user != nil {
                    Text("Welcome \(session.user!.displayName!)")
                } else {
                    Text("Please login")
                }
            }
        }
}

struct ContentView_Previews: PreviewProvider {
    let nav = UINavigationController()
    static var previews: some View {
        ContentView().environmentObject(SessionStore())
    }
}

In an extension on my sceneDelegate i implement the needed firebase functions. On succesfull login i create a new appuser object which i place in the sessionStore and then change the rootviewcontroller to the contentview passing in the environmentObject

Extension SceneDelegate {

    func authUI(_ authUI: FUIAuth, didSignInWith user: User?, error: Error?) {
        
        guard user != nil else {
            print("No User")
            return
        }
        
        print(user!.displayName!)
        
        let user = AppUser(uid: user!.uid,displayName: user?.email,email: user?.displayName)
        
        self.appSession.user = user
        let contentView = ContentView()
        self.window?.rootViewController = UIHostingController(rootView: contentView.environmentObject(SessionStore()))
        self.window?.makeKeyAndVisible()
        
    }
        
        
    func authPickerViewController(for authUI: FUIAuth) -> FUIAuthPickerViewController {
        return MyFUIAuthPickerViewController(authUI: authUI)
    }
}

Now when i test my app i get following error after entering my username and password

Fatal error: No ObservableObject of type SessionStore found. A View.environmentObject(_:) for SessionStore may be missing as an ancestor of this view.: file /BuildRoot/Library/Caches/com.apple.xbs/Sources/Monoceros_Sim/Monoceros-39.4.3/Core/EnvironmentObject.swift, line 55

I suspect this has to do with the fact that de environmentObject is lost in MyFUIAuthPickerViewController between the flow from my sceneDelegate to the ContentView but how do i prevent this from happening ? I need to somehow extend the MyFUIAuthPickerViewController to allow passing of the environmentObject but how ?

Hope my problem is clear and you guys can help.

Your code in the SceneDelegate is let contentView = ContentView() I think it should be something like let contentView = ContentView().environmentObject(SessionStore())

It also seems that you SessionStore is missing var didChange = PassthroughSubject<SessionStore, Never>()

The first lines of your SessionStore should be something like:

import Foundation
import SwiftUI
import Firebase
import Combine

class SessionStore : ObservableObject
{

    @Published var user: AppUser? { didSet { self.didChange.send(self) }}
    var didChange = PassthroughSubject<SessionStore, Never>()
    var handle: AuthStateDidChangeListenerHandle?

You want to make sure that changes are propagating to listeners (subscribers).

And if I'm correct @EnvironmentObject var appSession: SessionStore should not be mentioned in the SceneDelegate

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM