Note that there are some explanatory texts on larger screens.

plurals
  1. POapplication didFinishLaunchingWithOptions is called before Storyboards are done loading?
    primarykey
    data
    text
    <p>I have an app that implements a Facebook login at startup, using code pretty much exactly similar to this: <a href="https://developers.facebook.com/docs/tutorials/ios-sdk-tutorial/authenticate/" rel="nofollow noreferrer">Facebook Scrumptious Tutorial</a>, except with Storyboards.</p> <p>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.</p> <p>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:</p> <p>As far as I'm aware, the <em>application didFinishLaunchingWithOptions</em> method is <em>supposed</em> to be called <em>after</em> 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 (<em>Warning: Attempt to present &lt; QLoginViewController: 0x955c020> on &lt; UINavigationController: 0xa28c6e0> whose view is not in the window hierarchy!</em>).</p> <p>If I tell it to present the view after a delay however:</p> <pre><code>[self performSelector:@selector(showLoginViewAnimated:) withObject:NO afterDelay:.001]; </code></pre> <p>(where <em>showLoginViewAnimated:</em> is the method that tells the main view to present the login view), then it works fine.</p> <p>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...</p> <p>Here is my appDelegate <em>didFinishLaunchingWithOptions</em> code:</p> <pre><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; } </code></pre> <p>And here is the <em>showLoginViewAnimated:</em> code:</p> <pre><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]; } } </code></pre> <p>In the original, non-storyboard version of the app, the <em>showLoginViewAnimated:</em> method wouldn't be called in <em>application didFinishLaunchingWithOptions</em> until I had already manually created the views like so:</p> <pre><code>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]; </code></pre> <p>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 <em>application didFinishLaunchingWithOptions</em> 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...</p> <p>Any ideas? Thanks for any help!</p> <p><strong>EDIT:</strong> 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 <em>(BOOL)animated</em> parameter to the <em>showLoginView</em> method in my code, but that's for something else and doesn't change anything (I've checked).</p> <p>Here's the original (non-storyboard) appDelegate <em>didFinishLaunchingWithOptions</em> method:</p> <pre><code>- (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; } </code></pre> <p>And here's the original (non-storyboard) <em>showLoginView</em> method:</p> <pre><code>- (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]; } } </code></pre>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload