Firebase User Redirect - Auto Navigation for Logged-in Users

Complete guide to automatically redirect logged-in users to the main screen using Firebase authentication

Firebase User Redirect

Note:

This tutorial covers automatically redirecting logged-in users to the main screen, improving user experience by avoiding repeated login prompts. Make sure you have completed the Firebase integration, registration, and login setup from the previous tutorials.

Overview

Hello!

Requiring users who have previously logged into your app to enter their email and password every time can create negative results for user experience.

Firebase's ability to keep logged-in user information in memory using the "currentUser" resource makes the operations we'll perform much easier.

Reference:

Step 1: Determining Which Screen Logged-in Users Should Start With

First, decide which screen logged-in users should start with when entering the app.

Then, add an "Identifier" to the relevant screen in your Storyboard.

Storyboard Identifier Setup

Step 2: SceneDelegate Implementation

Go to the SceneDelegate.swift file where user interface information is stored. As you can see in the func scene description section, it's written that the var window = UIWindow? variable defined above the class can be used in this area.

Inside the scene function, first assign the logged-in user information to a variable:

let currentUser = Auth.auth().currentUser

Then, if there's a logged-in user, we want the user to start with the ID shown in the image above.

Let's start with an if statement.

For this, first select the Storyboard file we'll work with, then assign which view to start from in this Storyboard to a variable.

The UIWindow variable called rootViewController will determine the app's opening screen. The opening screen will be equal to the variable we defined above, and the code block we'll get will be as follows:

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    guard let windowScene = (scene as? UIWindowScene) else { return }
    
    let currentUser = Auth.auth().currentUser
    
    if currentUser != nil {
        // User is logged in - redirect to main screen
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        let mainViewController = storyboard.instantiateViewController(withIdentifier: "MainViewController")
        window?.rootViewController = mainViewController
    } else {
        // User is not logged in - show login screen
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        let loginViewController = storyboard.instantiateViewController(withIdentifier: "LoginViewController")
        window?.rootViewController = loginViewController
    }
    
    window?.makeKeyAndVisible()
}

This way, we will change the starting screen for logged-in users.

Step 3: Complete SceneDelegate Implementation

Here's the complete SceneDelegate.swift implementation:

import UIKit
import Firebase

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        guard let windowScene = (scene as? UIWindowScene) else { return }
        
        window = UIWindow(windowScene: windowScene)
        
        // Check if user is already logged in
        let currentUser = Auth.auth().currentUser
        
        if currentUser != nil {
            // User is logged in - redirect to main screen
            let storyboard = UIStoryboard(name: "Main", bundle: nil)
            let mainViewController = storyboard.instantiateViewController(withIdentifier: "MainViewController")
            window?.rootViewController = mainViewController
        } else {
            // User is not logged in - show login screen
            let storyboard = UIStoryboard(name: "Main", bundle: nil)
            let loginViewController = storyboard.instantiateViewController(withIdentifier: "LoginViewController")
            window?.rootViewController = loginViewController
        }
        
        window?.makeKeyAndVisible()
    }

    func sceneDidDisconnect(_ scene: UIScene) {
        // Called when the scene is being released by the system
    }

    func sceneDidBecomeActive(_ scene: UIScene) {
        // Called when the scene has moved from an inactive state to an active state
    }

    func sceneWillResignActive(_ scene: UIScene) {
        // Called when the scene will move from an active state to an inactive state
    }

    func sceneWillEnterForeground(_ scene: UIScene) {
        // Called as the scene transitions from the background to the foreground
    }

    func sceneDidEnterBackground(_ scene: UIScene) {
        // Called as the scene transitions from the foreground to the background
    }
}

Step 4: Logout Functionality

When the user performs a logout operation, you can assign the following code line to a button:

Note: When you enter the logout code, Xcode may inform you that you need to perform this operation with a try command because you might encounter an error message here. This issue can be resolved with the do-try-catch trio as follows:

@IBAction func logoutButtonTapped(_ sender: Any) {
    do {
        try Auth.auth().signOut()
        
        // Redirect to login screen after logout
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        let loginViewController = storyboard.instantiateViewController(withIdentifier: "LoginViewController")
        
        // Use a transition animation
        UIView.transition(with: window!, duration: 0.3, options: .transitionCrossDissolve, animations: {
            self.window?.rootViewController = loginViewController
        }, completion: nil)
        
    } catch {
        showAlert(titleInput: "Error", messageInput: "Failed to logout")
    }
}

Step 5: Enhanced User Experience

Smooth Transitions

Add smooth transitions between screens:

private func transitionToViewController(_ viewController: UIViewController) {
    UIView.transition(with: window!, duration: 0.3, options: .transitionCrossDissolve, animations: {
        self.window?.rootViewController = viewController
    }, completion: nil)
}

Loading States

Show loading states during authentication checks:

private func showLoadingScreen() {
    let loadingViewController = UIViewController()
    loadingViewController.view.backgroundColor = .white
    
    let activityIndicator = UIActivityIndicatorView(style: .large)
    activityIndicator.center = loadingViewController.view.center
    activityIndicator.startAnimating()
    
    loadingViewController.view.addSubview(activityIndicator)
    window?.rootViewController = loadingViewController
}

Step 6: Advanced Implementation

Multiple Screen Support

Handle different user roles and screens:

private func determineInitialScreen() -> UIViewController {
    let currentUser = Auth.auth().currentUser
    
    guard let user = currentUser else {
        return instantiateViewController(withIdentifier: "LoginViewController")
    }
    
    // Check user role or preferences
    if let userRole = UserDefaults.standard.string(forKey: "userRole") {
        switch userRole {
        case "admin":
            return instantiateViewController(withIdentifier: "AdminViewController")
        case "user":
            return instantiateViewController(withIdentifier: "MainViewController")
        default:
            return instantiateViewController(withIdentifier: "MainViewController")
        }
    }
    
    return instantiateViewController(withIdentifier: "MainViewController")
}

private func instantiateViewController(withIdentifier identifier: String) -> UIViewController {
    let storyboard = UIStoryboard(name: "Main", bundle: nil)
    return storyboard.instantiateViewController(withIdentifier: identifier)
}

Persistent Login State

Check for persistent login state:

private func checkPersistentLogin() -> Bool {
    // Check if user has chosen to stay logged in
    let stayLoggedIn = UserDefaults.standard.bool(forKey: "stayLoggedIn")
    
    if stayLoggedIn {
        return Auth.auth().currentUser != nil
    }
    
    return false
}

Step 7: Error Handling

Network Issues

Handle network connectivity issues:

private func checkNetworkConnectivity() {
    // Check if user has internet connection
    if !isNetworkAvailable() {
        // Show offline mode or cached data
        showOfflineMode()
    }
}

private func isNetworkAvailable() -> Bool {
    // Implement network connectivity check
    return true // Placeholder
}

private func showOfflineMode() {
    // Show cached data or offline message
    let offlineViewController = instantiateViewController(withIdentifier: "OfflineViewController")
    window?.rootViewController = offlineViewController
}

Authentication State Changes

Listen for authentication state changes:

private func setupAuthStateListener() {
    Auth.auth().addStateDidChangeListener { [weak self] (auth, user) in
        DispatchQueue.main.async {
            if user != nil {
                // User signed in
                self?.navigateToMainScreen()
            } else {
                // User signed out
                self?.navigateToLoginScreen()
            }
        }
    }
}

private func navigateToMainScreen() {
    let mainViewController = instantiateViewController(withIdentifier: "MainViewController")
    transitionToViewController(mainViewController)
}

private func navigateToLoginScreen() {
    let loginViewController = instantiateViewController(withIdentifier: "LoginViewController")
    transitionToViewController(loginViewController)
}

Note:

This implementation significantly improves user experience by automatically handling navigation based on authentication state. Remember to test thoroughly with different scenarios and user states before deploying to production.