I am checking if the user got a verification email after registration inside login function:
static func authenticate(withEmail email :String,
password:String,
completionHandler:@escaping (Result<Bool, EmailAuthError>) -> ()) {
Auth.auth().signIn(withEmail: email, password: password) { (authResult, error) in
// check the NSError code and convert the error to an AuthError type
var newError:NSError
if let err = error {
newError = err as NSError
var authError:EmailAuthError?
switch newError.code {
case 17009:
authError = .incorrectPassword
case 17008:
authError = .invalidEmail
case 17011:
authError = .accoundDoesNotExist
default:
authError = .unknownError
}
completionHandler(.failure(authError!))
} else {
if (Auth.auth().currentUser!.isEmailVerified == true){
completionHandler(.success(true))
print ("Email Verified")
}
else {
print ("Email not verified")
}
}
}
}
This function will be called after clicking "Login" button where I reload user to make sure that (isEmailVerified) value updated.
Button(action: {
//Sign In Action
FBAuth.reloadUser()
FBAuth.authenticate(withEmail: self.user.email, password: self.user.password) { (result) in
switch result {
case .failure(let error):
self.authError = error
self.showAlert = true
case .success( _):
print ("Signed in.")
}
}
}
The Content View where I am checking the user's login status, I did not update it after I added isEmailVerified to my code coz I have only three cases undefined, signedIn, signedOut, I don't know how to get out of this box:
struct ContentView: View {
@EnvironmentObject var userInfo : UserInfo
@State var shouldShowSignUp : Bool = false
var body: some View {
Group {
if shouldShowSignUp {
SignupView()
}
else {
ZStack{
if userInfo.isUserAuthenticated == .undefined{ SignupView()}
else if userInfo.isUserAuthenticated == .signedIn && Auth.auth().currentUser!.isEmailVerified == true {HomeView()}
else if userInfo.isUserAuthenticated == .signedIn && Auth.auth().currentUser!.isEmailVerified == false {LoginView()}
SplashScreen()
}
}
}
.onAppear {
self.userInfo.configureFirebaseStateDidChange()
}
}}
UserInfo :
import Foundation
import FirebaseAuth
class UserInfo : ObservableObject{
enum FBAuthState{
case undefined, signedOut, signedIn
}
@Published var isUserAuthenticated : FBAuthState = .undefined
@Published var user : FBUser = .init(uid: "", name: "", email: "")
var authStateDidChangeListenerHandle : AuthStateDidChangeListenerHandle?
func configureFirebaseStateDidChange (){
authStateDidChangeListenerHandle = Auth.auth().addStateDidChangeListener({ (_,user) in
guard let _ = user else {
self.isUserAuthenticated = .signedOut
return
}
self.isUserAuthenticated = .signedIn
})
}
}
Everything is going fine. I get the correct value for isEmailVerified. My question is how to give verified users access to the homeView and unverified users they will get alert that their email needs to be verified and they will stay in the loginView.
CodePudding user response:
As you didn´t post your Views I have to answer with an abstract answer.
in your main App struct
@main
struct TestApp1App: App {
@Environment(\.scenePhase) private var scenePhase
@AppStorage("emailVerified") private var emailVerified: Bool = false
var body: some Scene {
WindowGroup {
if emailVerified{
LoginView()
}else{
HomeView()
}
}
}
}
And in your LoginView:
// Add this
@AppStorage("emailVerified") private var emailVerified: Bool = false
Button(action: {
//Sign In Action
FBAuth.reloadUser()
FBAuth.authenticate(withEmail: self.user.email, password: self.user.password) { (result) in
switch result {
case .failure(let error):
self.authError = error
self.showAlert = true
case .success( _):
print ("Signed in.")
emailVerified = true //add this
}
}
}
Edit:
But this doesn´t change the prime layout of my answer. For example you don´t persist the verificationState. So you have to do it all over again.
In your case create the UserInfo
class in your app struct and check the isUserAuthenticated
property in the if statement.
Edit 2:
This can be simplyfied in a lot of ways. My suggestion:
ContentView should be something like this:
struct ContentView: View {
@EnvironmentObject var userInfo : UserInfo
// pull the vars from persisted store this will also update your view when changed
@AppStorage("authState") var authState: FBAuthState = .undefined
@AppStorage("emailVerificationState") var emailVerificationState: Bool = false
var body: some View {
Group{
if authState == .undefined || !emailVerificationState{
SignupView()
} else {
ZStack{
if authState == .signedIn{
HomeView()
} else{
LoginView()
}
SplashScreen()
}
}
}
.onAppear {
self.userInfo.configureFirebaseStateDidChange()
}
}
}
Change your UserInfo to:
enum FBAuthState: Int{
case undefined, signedOut, signedIn
}
class UserInfo : ObservableObject{
@AppStorage("authState") var authState: FBAuthState = .undefined
@Published var user : FBUser = .init(uid: "", name: "", email: "")
var authStateDidChangeListenerHandle : AuthStateDidChangeListenerHandle?
func configureFirebaseStateDidChange (){
authStateDidChangeListenerHandle = Auth.auth().addStateDidChangeListener({ (_,user) in
guard let _ = user else {
self.authState = .signedOut
return
}
self.authState = .signedIn
})
}
}
I don´t know where this button lives but in that class or view add:
@AppStorage("emailVerificationState") var emailVerificationState: Bool = false
and do:
Button(action: {
//Sign In Action
FBAuth.reloadUser()
FBAuth.authenticate(withEmail: self.user.email, password: self.user.password) { (result) in
switch result {
case .failure(let error):
emailVerificationState = false
self.authError = error
self.showAlert = true
case .success( _):
emailVerificationState = true
print ("Signed in.")
}
}
}
Edit 3:
- You would need to import SwiftUI module where you want to use @AppStorage property wrapper.
- Please see my answer. I moved FBAuthState out of your class to make it visible outside. And you would need to derive from
Int
.
Edit 4:
Group{
if authState == .undefined {
SignupView()
} else {
ZStack{
if authState == .signedIn && emailVerificationState{
HomeView()
} else{
LoginView()
}
SplashScreen()
}
}
}