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
class SceneDelegate: UIResponder, UIWindowSceneDelegate, FUIAuthDelegate{
var window: UIWindow?
var authUI: FUIAuth?
@EnvironmentObject var appSession: SessionStore
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.