Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

application didFinishLaunchingWithOptions is called before Storyboards are done loading?

I have an app that implements a Facebook login at startup, using code pretty much exactly similar to this: Facebook Scrumptious Tutorial, except with Storyboards.

The basic gist of the code is that on app startup the app delegate checks to see if you're logged in already, and if you are it goes straight to the main view, and if not, it asks the main view to present a login view so the user can log in.

I've already solved my other issue with being able to tell the main view in the storyboard to present a different view, by getting the main view from the view heirarchy, and then calling a segue on the view. That all works fine, however I've got one last problem to solve:

As far as I'm aware, the application didFinishLaunchingWithOptions method is supposed to be called after the storyboards have been fully loaded. However in my code, if I try to tell the main view to present another view, it gives me an error basically saying it isn't loaded yet (Warning: Attempt to present < QLoginViewController: 0x955c020> on < UINavigationController: 0xa28c6e0> whose view is not in the window hierarchy!).

If I tell it to present the view after a delay however:

[self performSelector:@selector(showLoginViewAnimated:) withObject:NO afterDelay:.001];

(where showLoginViewAnimated: is the method that tells the main view to present the login view), then it works fine.

Can anyone help me figure out what's going wrong here, and how I might be able to fix it? Performing the selector with a delay is obviously a bad workaround, as I can never know if a different device might take longer to load the views...

Here is my appDelegate didFinishLaunchingWithOptions code:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // Override point for customization after application launch.

    self.navigationController = (UINavigationController *)self.window.rootViewController;

    // Navigation Bar Color
    [[UINavigationBar appearance] setTintColor:[UIColor colorWithRed:255.0/255.0 green:128.0/255.0 blue:60.0/255.0 alpha:1.0]];


    /* Facebook Login */    // THIS IS THE RELEVANT CODE: 
    // See if we have a valid token for the current state
    if (FBSession.activeSession.state == FBSessionStateCreatedTokenLoaded) {
        // Yes, valid token exists - open the session (don't display login page)
        [self openSession];
    } else {
        // No, valid token does not exist -  display the login page.
        if ([self.navigationController isViewLoaded]) {
            [self showLoginViewAnimated:NO];   // MY ATTEMPT TO AVOID USING THE DELAY IF POSSIBLE
        }
        else {
            [self performSelector:@selector(showLoginViewAnimated:) withObject:NO afterDelay:.001]; // Delay needed to allow for storyboard loading
        }
    }

    return YES;
}

And here is the showLoginViewAnimated: code:

- (void)showLoginViewAnimated:(BOOL)animated
{
    UIViewController *topViewController = [self.navigationController topViewController];
    UIViewController *presentedViewController = [topViewController presentedViewController];

    // If the login screen is not already displayed, display it. If the login screen is
    // displayed, then getting back here means the login in progress did not successfully
    // complete. In that case, notify the login view so it can update its UI appropriately.

    if (![presentedViewController isKindOfClass:[QLoginViewController class]]) {
        if (animated) {
            [topViewController performSegueWithIdentifier:@"ShowLoginViewAnimated" sender:self];
        }
        else {
            [topViewController performSegueWithIdentifier:@"ShowLoginViewStatic" sender:self];
        }
    }
    else {
        QLoginViewController *loginViewController = (QLoginViewController *)presentedViewController;
        [loginViewController loginFailed];
    }
}

In the original, non-storyboard version of the app, the showLoginViewAnimated: method wouldn't be called in application didFinishLaunchingWithOptions until I had already manually created the views like so:

self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
self.viewController = [[MainViewController alloc] initWithNibName:@"MainViewController" bundle:nil];
self.navController = [[UINavigationController alloc] initWithRootViewController:self.viewController];

self.window.rootViewController = self.navController;
[self.window makeKeyAndVisible];

So I would already know 100% for sure that the views exist before I try to tell them to present new views. In the storyboard version of the app however, none of that code exists, so I simply have to trust that theoretically the application didFinishLaunchingWithOptions method isn't called until the storyboard views are fully loaded - however, that doesn't seem to be the case. Perhaps it is doing it asynchronously instead? I have no idea...

Any ideas? Thanks for any help!

EDIT: Here is the original code from the tutorial, which works perfectly fine - and does almost exactly the same thing, just with nibs. I've added the (BOOL)animated parameter to the showLoginView method in my code, but that's for something else and doesn't change anything (I've checked).

Here's the original (non-storyboard) appDelegate didFinishLaunchingWithOptions method:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.
    self.viewController = [[FBLViewController alloc] initWithNibName:@"FBLViewController" bundle:nil];
    self.navController = [[UINavigationController alloc] initWithRootViewController:self.viewController];

    self.window.rootViewController = self.navController;
    [self.window makeKeyAndVisible];


    // See if we have a valid Facebook token for the current state
    if (FBSession.activeSession.state == FBSessionStateCreatedTokenLoaded) {
        // Yes, so just open the session (this won't display any UX).
        [self openSession];
    }
    else {
        // No, display the login page.
        [self showLoginView];
    }

    return YES;
}

And here's the original (non-storyboard) showLoginView method:

- (void)showLoginView
{
    UIViewController *topViewController = [self.navController topViewController];
    UIViewController *presentedViewController = [topViewController presentedViewController];

    // IF the login screen is not already displayed, display it. If the login screen is
    // displayed, then getting back here means the login in progress did not successfully
    // complete. In that case, notify the login view so it can update its UI appropriately.

    if (![presentedViewController isKindOfClass:[FBLLoginViewController class]]) {
        FBLLoginViewController *loginViewController = [[FBLLoginViewController alloc] initWithNibName:@"FBLLoginViewController" bundle:nil];
        loginViewController.delegate = self;
        [topViewController presentViewController:loginViewController animated:NO completion:nil];
    }
    else {
        FBLLoginViewController *loginViewController = (FBLLoginViewController *)presentedViewController;
        [loginViewController loginFailed];
    }
}
like image 230
Fateh Khalsa Avatar asked Apr 27 '13 22:04

Fateh Khalsa


1 Answers

Add this line to didFinishLaunchingWithOptions:

[self.window makeKeyAndVisible];

before the Facebook login code.

like image 57
Fateh Khalsa Avatar answered Sep 28 '22 07:09

Fateh Khalsa