1

I am developing an iOS App that has several Tabs. So I decided my root view Controller will be a Tab Bar Controller with 2 views. I need the user to login as soon as the application starts, so I'm trying to find out the best approach to do this.

Following other similar questions posted here I've tried this 2 approaches:

  1. Presenting the login screen as a Modal View on top of the Tab Bar Controller

  2. Instantiating the Login view controller

I've done this, by using a UserisLoggedIn flag in NSUserDefaults that is set every time the user logs in or logs out.

The problem I face with both approaches is that before the login screen appears there's a quick flickr of the first view in the Tab Bar. I've tried to resolve this setting the alpha value of the Tab Bar to 0 if the user is not logged in for the modal approach, but for the instance approach it doesnt work, it still flickers before showing the login screen. Anyway, I find this solution tedious and not really elegant.

There must be a standard approach for doing this in iOS, since you have loads of apps that present first a login screen, but since I am a beginner with iOS Programming I still don't know how to do it.

Here's some code from my Tab bar controller:

1st Approach

Tab Bar Controller

 override func viewDidLoad() {
        super.viewDidLoad()
        view.alpha = 0

        let defaults = NSUserDefaults.standardUserDefaults()

        if defaults.objectForKey("userLoggedIn") as NSString == "loggedIn"{
            view.alpha = 1.0
        }

      }


override func viewDidAppear(animated: Bool) {
        super.viewDidAppear(false)
        self.showLoginView()

    }



func showLoginView(){

        let defaults = NSUserDefaults.standardUserDefaults()

        if defaults.objectForKey("userLoggedIn") == nil{
            self.performSegueWithIdentifier("loginView", sender: self)
        }
    }

Login View Controller

func updateUserLoggedInFlag() {
        // Update the NSUserDefaults flag
        let defaults = NSUserDefaults.standardUserDefaults()
        defaults.setObject("loggedIn", forKey: "userLoggedIn")
        defaults.synchronize()
    }

@IBAction func login(sender: AnyObject) {

//Do my login here...
//If login successful:

    self.performSegueWithIdentifier("dismissLogin", sender: self)

}

2nd Approach, Creating an Instance of the Login View Controller

1st View in the Tab Bar

override func viewDidAppear(animated: Bool){
        super.viewDidAppear(true)

        let defaults = NSUserDefaults.standardUserDefaults()
        if defaults.objectForKey("userLoggedIn") == nil{

            let loginController: LoginViewController = self.storyboard?.instantiateViewControllerWithIdentifier("LoginViewController") as LoginViewController
            self.tabBarController?.presentViewController(loginController, animated: false, completion: nil)

}

Login View Controller

@IBAction func login(sender: AnyObject) {

//Do my login here...
    //If login successful:
    self.dismissViewControllerAnimated(true, completion: nil)
}
EduCodes
  • 77
  • 8

3 Answers3

3

OK here is what did the trick for me. I don't know if it's the right thing to do, but it seems to work. Like Joseph Duffy mentioned all I did was to set the Login view as the rootviewcontroller in the delegate, and once I was succesfully logged in, switch back to the tab bar controller as rootviewcontroller.

Code in AppDelegate:

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {

    let defaults = NSUserDefaults.standardUserDefaults()
            if defaults.objectForKey("userLoggedIn") == nil{         
                showLogin()   
            }

}

 func showLogin() {

        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        let loginViewController = storyboard.instantiateViewControllerWithIdentifier("LoginViewController") as LoginViewController
        self.window?.makeKeyAndVisible()
        self.window?.rootViewController = loginViewController
 }

Code in LoginViewController

@IBAction func login(sender: AnyObject) {
   //Do my login here...
   //If login successful:
     let storyboard = UIStoryboard(name: "Main", bundle: nil)
     let appDelegate: AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
     self.dismissViewControllerAnimated(true, completion: nil)
     appDelegate.window?.rootViewController = storyboard.instantiateViewControllerWithIdentifier("tabBarID") as TabBarController
}
EduCodes
  • 77
  • 8
  • 1
    I've used the same with a few small modifications. In swift 1.2+ the `as` will now be `as!` when getting the AppDelegate also, when setting the rootViewController, I used `appDelegate.window?.rootViewController = storyboard.instantiateInitialViewController()`. The last change I made, that is not that important was that I changed `window?.makeKeyAndVisible()` for `window?.makeKeyWindow()` as it will be made visible when didFinishLaunching returns true – LostBalloon Nov 07 '15 at 16:23
  • This solution also worked for me. Can somebody point out why the other solutions give this "unbalanced call" warning? – Corona Jan 14 '16 at 17:17
2

Take a look at a similar question's answer. The solution is to do this check in your app delegate's application:didFinishLaunchingWithOptions: method by checking if the user is logged in and loading the login view from a storyboard when necessary.

I have created a blank project, added a view controller with a storyboard identifier of loginScreen and have the following code in the app delegate:

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
    // Override point for customization after application launch.
    self.showLogin()

    return true
}

func showLogin() {
    //loginScreen
    let storyboard = UIStoryboard(name: "Main", bundle: nil)
    let loginViewController = storyboard.instantiateViewControllerWithIdentifier("loginScreen") as UIViewController
    if let window = self.window {
        window.makeKeyAndVisible()
        window.rootViewController?.presentViewController(loginViewController, animated: false, completion: nil)
    }
}

This shows the login screen and the main screen is not seen.

Community
  • 1
  • 1
Joseph Duffy
  • 4,566
  • 9
  • 38
  • 68
  • Doing this in the AppDelegate didn't fix the flickering problem. The first View from the Tab Bar Controller still quickly appears before the login screen shows up. – EduCodes Mar 20 '15 at 10:28
  • Are you performing the segue in the app delegate or creating the view controller and showing as per the linked answer? – Joseph Duffy Mar 20 '15 at 10:31
  • I am creating the view controller in the app delegate as shown in the answer – EduCodes Mar 20 '15 at 10:39
  • When I have done this method in the past it has worked. Can you post your new code? – Joseph Duffy Mar 20 '15 at 10:41
  • Plus it's giving me this warnings: 2015-03-20 12:50:09.695 APPView StableNet[5290:585692] Unbalanced calls to begin/end appearance transitions for . 2015-03-20 12:50:09.699 APPView StableNet[5290:585692] Warning: Attempt to present on whose view is not in the window hierarchy! – EduCodes Mar 20 '15 at 11:50
  • Right now the start arrow is in the tab bar in my storyboard, should I change it to the loginview storyboard? Should I just remove it? – EduCodes Mar 20 '15 at 12:01
  • Take a look at my edit. I have started a fresh project and it is working for me. It does give the "Unbalanced calls to begin/end appearance transitions" error, but works as intended. – Joseph Duffy Mar 20 '15 at 12:21
  • Still having the same issues. The view herarchy error and the first tab quickly shows up before the login screen appears – EduCodes Mar 20 '15 at 12:25
  • Then you may have to set the value of `window.rootViewController` to be the login view controller and then when the login has finished swap it out with the tab view controller. I can't recreate your issue so I'm not sure what else to say – Joseph Duffy Mar 20 '15 at 12:29
2
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
                // Override point for customization after application launch.

         let defaults = NSUserDefaults.standardUserDefaults()
          if defaults.objectForKey("userLoggedIn") == nil{

          showLoginView()

   }

func showLoginView(){

        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        let loginViewController: LoginViewController = storyboard.instantiateViewControllerWithIdentifier("LoginViewController") as LoginViewController
        self.window?.makeKeyAndVisible()
        self.window?.rootViewController?.presentViewController(loginViewController, animated: true, completion: nil)
    }
EduCodes
  • 77
  • 8