Fadel Benhamza
Fadel Benhamza

Reputation: 31

viewDidAppear is delayed by 0.5 sec

Greetings StackOverflow!

I'm really new to Swift development, so don't go all crazy on me please :3

I've created a login page for my mobile app (Firebase Email + Password)

I've implemented an "auto login" function - so when a user logs in for the first time, the user stays logged after closing the app & opening it.

The problem is that every time a user opens the app, there is a slight delay between viewDidLoad and viewDidAppear -> resulting in that every time the app opens, you can see the login screen for about 0.4 sec until it automatically sings the user in.

Once the user logs in the user are segued (no animation) to my UITabBarController.

I've provided the code for my LoginViewController.

LoginViewController is my first Controller the app loads in.

Best regards

class LoginViewController: UIViewController {

    @IBOutlet weak var loginEmail: UITextField!
    @IBOutlet weak var loginPassword: UITextField!
    @IBOutlet weak var loginBorder: UIButton!

    let userDefault = UserDefaults.standard

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func viewDidAppear(_ animated: Bool) {
        if userDefault.bool(forKey: "usersignedin") {
            performSegue(withIdentifier: "login", sender: self)        
        }

        //Border color button//
        loginBorder.layer.borderColor = UIColor.white.cgColor
        //Border color button//

        //Hide Keyboard Use
        self.hideKeyboardWhenTappedAround()
        //Hide Keyboard Use

        //start padding function for login
        addPaddingAndBorder(to: loginEmail)
        addPaddingAndBorder(to: loginPassword)
        //start padding function for login
    }

    @IBAction func loginButton(_ sender: UIButton) {
        Auth.auth().signIn(withEmail: loginEmail.text!, password: loginPassword.text!) { (user, error) in
            if user != nil {
                self.userDefault.set(true, forKey: "usersignedin")
                self.userDefault.synchronize()
                self.performSegue(withIdentifier: "login", sender: self)
            } else {
                let alert = UIAlertController(title: "Invalid Email or Password", message: nil, preferredStyle: .alert)
                let okButton = UIAlertAction(title: "Ok", style: .default, handler: nil)
                alert.addAction(okButton)
                self.present(alert, animated: true, completion: nil)              
            }
        }
    }
}

Upvotes: 3

Views: 764

Answers (6)

Ganesh Bavaskar
Ganesh Bavaskar

Reputation: 764

Best way is to decide in AppDelegate which controller to load based on Login Status

        let navigationController = UINavigationController()
        navigationController.navigationBar.barStyle = .black
        let storyboard = UIStoryboard(name: "Main", bundle: nil)

        if UserDefaults.standard.object(forKey: "Token") != nil{
            let initialViewController = storyboard.instantiateViewController(withIdentifier: "HomeViewController")
            navigationController.viewControllers = [initialViewController]
        } else{
            let initialViewController = storyboard.instantiateViewController(withIdentifier: "LoginViewController")
            navigationController.viewControllers = [initialViewController]
        }

        navigationController.navigationBar.barTintColor = UIColor.black
        navigationController.navigationBar.tintColor = UIColor.white
        navigationController.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor : UIColor.white]
        self.window?.rootViewController = navigationController
        self.window?.makeKeyAndVisible()

Upvotes: 1

Andy
Andy

Reputation: 753

We're doing something similar to this in my current project, but we decide which controller to show first in the AppDelegate.

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

    var storyboard: UIStoryboard!
    if isLoggedIn {
        storyboard = UIStoryboard(name: "main", bundle: nil)            
    }
    else {
        storyboard = UIStoryboard(name: "login", bundle: nil)
    }

    let viewController = storyboard.instantiateInitialViewController()!
    viewController.loadViewIfNeeded()
    window.rootViewController = viewController
}

This has the advantage that when the app loads it immediately take you to the correct ViewController without showing the wrong one briefly first.

Upvotes: 4

MrAlirezaa
MrAlirezaa

Reputation: 398

viewDidAppear(_:) happens right after the view appears. I suggest moving your code from viewDidAppear(_:) to viewWillAppear(_:).

you can also add your login code in the viewDidLoad() because this method is called even before viewWillAppear(_:).

in your case your code will be:

override func viewDidLoad() {
    super.viewDidLoad()

    if userDefault.bool(forKey: "usersignedin") {
        performSegue(withIdentifier: "login", sender: self)
    }
}

More info:

when a view is loading, there are different methods which get called in a specific order:

  1. viewDidLoad() (once and only when the object is being created.)
  2. viewWillAppear(_:)
  3. [View appears on the screen]
  4. viewDidAppear(_:)

when you push another view controller over this one, this view controller won't be removed from the view controllers' stack. the important thing is when you come back to this view controller, this time viewDidLoad() won't get called. so only these methods get called again in this order:

  1. viewWillAppear(_:)
  2. [View appears on the screen]
  3. viewDidAppear(_:)

Upvotes: 1

PGDev
PGDev

Reputation: 24341

Move the call to performSegue(withIdentifier:sender:) to viewWillAppear(_:) instead of viewDidAppear(_:) in case usersignedin = true.

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)

    if userDefault.bool(forKey: "usersignedin") {
        performSegue(withIdentifier: "login", sender: self)
    }
}

Also, the response of signIn method is not received on the main thread. Since you're not updating the UI on the main thread, that's why it is resulting in the delay.

Embed the UI part in closure of signIn method in DispatchQueue.main.async

Auth.auth().signIn(withEmail: loginEmail.text!, password: loginPassword.text!) { (user, error) in
    if user != nil {
        self.userDefault.set(true, forKey: "usersignedin")
        self.userDefault.synchronize()
        DispatchQueue.main.async { //here..........
            self.performSegue(withIdentifier: "login", sender: self)
        }
    }
    else {
        let alert = UIAlertController(title: "Invalid Email or Password", message: nil, preferredStyle: .alert)
        let okButton = UIAlertAction(title: "Ok", style: .default, handler: nil)
        alert.addAction(okButton)
        DispatchQueue.main.async { //here..........
            self.present(alert, animated: true, completion: nil)
        }
    }
}

Upvotes: 1

Dhruv Jindal
Dhruv Jindal

Reputation: 21

Change the order of view controllers. Always load your UITabBarController first (as initial controller). Then in your UITabBarController viewDidLoad, check if the user is logged-in or not. If not, then show the login screen. This way your UITabBarController is always loaded first and then based on auto-logn condition, your login screen is displayed. So when the user is auto looged-in then your delay issue will not come.

I have used this concept in all of my apps and it works fine.

Upvotes: 1

Maximilian Litteral
Maximilian Litteral

Reputation: 3089

This is correct system behavior. ViewDidLoad will always be called much sooner than ViewDidAppear. IF you want to change the base view controller based on userDefault.bool(forKey: "usersignedin"), add that logic to where you set the main view controller and swap them if already signed in.

Upvotes: 1

Related Questions