I'm currently building an iOS app that will synchronize account information from Firestore. I have the login/register process hooked up and working. However, I need help understanding how to update my logInOutBtn
, fullNameTxt
and emailTxt
in my MenuVC
automatically when an user logs in/out. Currently, it will update whenever I close then reopen the menu, but what should I use to automatically update it without having to close the menu? Thanks!
// MenuVC
override func viewDidAppear(_ animated: Bool) {
if let user = Auth.auth().currentUser , !user.isAnonymous {
// We are logged in
logInOutBtn.setTitle("Logout", for: .normal)
if UserService.userListener == nil {
UserService.getCurrentUser {
self.fullNameTxt.text = UserService.user.fullName
self.emailTxt.text = UserService.user.email
}
}
} else {
logInOutBtn.setTitle("Login", for: .normal)
self.fullNameTxt.text = "Sign in or create an account"
self.emailTxt.text = "to continue."
}
}
fileprivate func presentLoginController() {
let storyboard = UIStoryboard(name: Storyboard.LoginStoryboard, bundle: nil)
if #available(iOS 13.0, *) {
let controller = storyboard.instantiateViewController(identifier: StoryboardId.LoginVC)
present(controller, animated: true, completion: nil)
} else {
// Fallback on earlier versions
}
}
@IBAction func logInOutClicked(_ sender: Any) {
guard let user = Auth.auth().currentUser else { return }
if user.isAnonymous {
presentLoginController()
} else {
do {
try Auth.auth().signOut()
UserService.logoutUser()
Auth.auth().signInAnonymously { (result, error) in
if let error = error {
debugPrint(error)
Auth.auth().handleFireAuthError(error: error, vc: self)
}
self.presentLoginController()
}
} catch {
debugPrint(error)
Auth.auth().handleFireAuthError(error: error, vc: self)
}
}
}
// UserService
func getCurrentUser(completion: @escaping () -> ()) {
guard let authUser = auth.currentUser else { return }
let userRef = db.collection("users").document(authUser.uid)
userListener = userRef.addSnapshotListener({ (snap, error) in
if let error = error {
debugPrint(error.localizedDescription)
return
}
guard let data = snap?.data() else { return }
self.user = User.init(data: data)
completion()
})
// User Model
struct User {
var fullName: String
var address: String
var id: String
var email: String
var stripeId: String
init(fullName: String = "",
address: String = "",
id: String = "",
email: String = "",
stripeId: String = "") {
self.fullName = fullName
self.address = address
self.id = id
self.email = email
self.stripeId = stripeId
}
init(data: [String : Any]) {
fullName = data["fullName"] as? String ?? ""
address = data["address"] as? String ?? ""
id = data["id"] as? String ?? ""
email = data["email"] as? String ?? ""
stripeId = data["stripeId"] as? String ?? ""
}
static func modelToData(user: User) -> [String : Any] {
let data : [String : Any] = [
"fullName" : user.fullName,
"address" : user.address,
"id" : user.id,
"email" : user.email,
"stripeId" : user.stripeId
]
return data
}
}
// My app menu
The signout process is pretty straightforward and is marked as throws so if it fails, it will generate an error that can be handled by a catch. It is not asynchronous so it won't have (or need) a closure.
So simply stated
func signOut() {
let firebaseAuth = Auth.auth()
do {
try firebaseAuth.signOut()
print("successful signout")
self.logInOutBtn.setTitle("Log In", for: .normal)
self.fullNameTxt.text = ""
self.emailTxt.text = ""
} catch let signOutError as NSError {
print ("Error signing out: %@", signOutError)
//present the error to the user/handle the error
}
}
The signIn function is asynchronous with a closure so when the user signs in successfully, the code in the closure will fire and that's the perfect place to update the UI.
Auth.auth().signIn(withEmail: email, password: password) { [weak self] authResult, error in
guard let strongSelf = self else { return }
// update the UI here.
}
You can also just monitor the authState with an observer and have it react to users logging in/out
self.authListener = Auth.auth()?.addAuthStateDidChangeListener { auth, user in
if let theUser = user {
print("User logged in \(theUser)") // User is signed in.
self.dismissViewControllerAnimated(true, completion: nil)
} else {
print("Need to login.") // No user is signed in.
//present login view controller
}
}
If you no longer want to observe the auth state, you can remove it with
Auth.auth()?.removeAuthStateDidChangeListener(self.authListener)