skip to Main Content

I’m trying to learn how to do basic login in swift with Firebase and it’s causing me to lose my mind over navigating to the main page of the app once the login is complete. I have a ViewModel that manages the login, and in the view I have added an onReceive property to listen on a viewModel’s boolean to detect sign in and trigger the navigation. I don’t understand why it’s not working, any help would be greatly appreciated!

ViewModel:

class LoginViewModel: ObservableObject {
    let auth = Auth.auth()
    var userInfo: User?
    
    @Published var isSignedIn = false
    
    func signIn(email: String, password: String) {
        auth.signIn(withEmail: email, password: password) { _, error in
            if let error = error as? NSError {
                print("Error happened on login"+error.description)
            } else {
                print("Login successful")
                if let user = self.auth.currentUser {
                    print("We have a user")
                    self.userInfo = user
                    self.isSignedIn.toggle()
                }
            }
        }
    }
}

View:

struct LoginPage: View {
    @State var email = ""
    @State var password = ""

    @ObservedObject var viewModel = LoginViewModel()

    var body: some View {
        NavigationView {
            ZStack {
                VStack {
                    TextField("Email", text: $email).padding()
                        .background(Color(.secondarySystemBackground))
                        .padding()
                    TextField("Password", text: $password).padding()
                        .background(Color(.secondarySystemBackground))
                        .padding()
                    Button(action: {
                        guard !email.isEmpty, !password.isEmpty else {
                            return
                        }
                        viewModel.signIn(email: email, password: password)

                    }, label: {
                        Text("Sign in")
                            .padding(EdgeInsets(top: 12, leading: 35, bottom: 12, trailing: 35))
                            .foregroundColor(.white)
                            .background(Color.blue)
                            .cornerRadius(15)
                    })
                }
            }.navigationTitle("Login")
        }.onReceive(viewModel.$isSignedIn) { isSignedIn in
            if isSignedIn {
                print("ok damm")
                NavigationLink(destination: HomePage()) {
                    EmptyView()
                }
            }
        }
    }
}

Note that "ok damm" prints every time.

3

Answers


  1. Chosen as BEST ANSWER

    Answer by Jatin Bhuva works but is deprecated for iOS 16. Here's the solution for new iOS versions:

    struct LoginPage: View {
        @State var email = ""
        @State var password = ""
    
        @ObservedObject var viewModel = LoginViewModel()
    
        var body: some View {
            NavigationStack {
                ZStack {
                    VStack {
                        TextField("Email", text: $email).padding()
                            .background(Color(.secondarySystemBackground))
                            .padding()
                        TextField("Password", text: $password).padding()
                            .background(Color(.secondarySystemBackground))
                            .padding()
                        Button(action: {
                            guard !email.isEmpty, !password.isEmpty else {
                                return
                            }
                            viewModel.signIn(email: email, password: password)
    
                        }, label: {
                            Text("Sign in")
                                .padding(EdgeInsets(top: 12, leading: 35, bottom: 12, trailing: 35))
                                .foregroundColor(.white)
                                .background(Color.blue)
                                .cornerRadius(15)
                        })
                        Text("Don't have an account yet ?").padding(EdgeInsets(top: 20, leading: 0, bottom: 10, trailing: 0))
                        Button(action: {
                            guard !email.isEmpty, !password.isEmpty else {
                                return
                            }
                            viewModel.signUp(email: email, password: password)
                        }, label: {
                            Text("Create an account")
                                .padding(EdgeInsets(top: 12, leading: 35, bottom: 12, trailing: 35))
                                .foregroundColor(.white)
                                .background(Color.blue)
                                .cornerRadius(15)
                        })
                    }
                }.navigationTitle("Login")
                    .navigationDestination(isPresented: $viewModel.isSignedIn) {
                        HomePage()
                    }
            }
        }
    }
    

    Notice how I replaced the NavigationView with a NavigationStack and added .navigationDestination(isPresented:) that listens for changes on my model's isSignedIn published property.


  2. Try the below code it will help you to solve your issue:

    NavigationLink(destination: Text("Second View"), isActive: $isSignedIn){
                    EmptyView()
                }
    

    View:

    struct LoginPage: View {
        @State var email = ""
        @State var password = ""
    
        @ObservedObject var viewModel = LoginViewModel()
    
        var body: some View {
            NavigationView {
                ZStack {
                    VStack {
                        TextField("Email", text: $email).padding()
                            .background(Color(.secondarySystemBackground))
                            .padding()
                        TextField("Password", text: $password).padding()
                            .background(Color(.secondarySystemBackground))
                            .padding()
                        Button(action: {
                            guard !email.isEmpty, !password.isEmpty else {
                                return
                            }
                            viewModel.signIn(email: email, password: password)
    
                        }, label: {
                            Text("Sign in")
                                .padding(EdgeInsets(top: 12, leading: 35, bottom: 12, trailing: 35))
                                .foregroundColor(.white)
                                .background(Color.blue)
                                .cornerRadius(15)
                        })
                    }
    
                  NavigationLink(destination: HomePage()), isActive: $isSignedIn){
                    EmptyView()
                }
                }.navigationTitle("Login")
            }
        }
    }
    
    Login or Signup to reply.
  3. switch statement in swiftui can handle that easily. I use it all the time;

    //Added code
    enum LoginStages {
        case preLogin, postLogin
    }
    
    class LoginViewModel: ObservableObject {
        let auth = Auth.auth()
        var userInfo: User?
        @Published var loginStage: LoginStages = LoginStages.preLogin 
    //Added code
    
        @Published var isSignedIn = false
    
    func signIn(email: String, password: String) {
        auth.signIn(withEmail: email, password: password) { _, error in
            if let error = error as? NSError {
                print("Error happened on login"+error.description)
            } else {
                print("Login successful")
                if let user = self.auth.currentUser {
                    print("We have a user")
                    self.userInfo = user
                    self.isSignedIn.toggle()
                    loginStage = .postLogin //Added code
                  }
               }
            }
         }
      }
    

    And your view should look like this;

    struct LoginPage: View {
      @State var email = ""
      @State var password = ""
    
      @ObservedObject var viewModel = LoginViewModel()
    
      var body: some View {
       switch viewModel.loginStage {
       case .preLogin:
       //This is ur initial code which the user sees before login.
       // the same as the code you have above.
      case .postLogin:
      //Here, just call the post login view after a successful login;
      // I am assuming it is called home view for this example.
      HomeView()
     
        }
      }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search