EDIT, July 2017. Starting from the first writing, I changed my practice to one where I run tasks on my own view controller. In this VC, I check the launch conditions, if necessary, click the busy interface, etc. Based on the state at startup, I set the root of the VC window.
In addition to solving the OP problem, this approach has additional advantages that allow better control over the launch of UI and UI transitions. Here's how to do it:
In the main storyboard, add a new VC called LaunchViewController and make it the initial application vc. Give your application a "real" initial vc identifier of the type "AppUI" (the identifiers are on the "Identification" tab in IB).
Identify other vcs that are the launches of the main user interface threads (e.g. Registration / Login, Tutorial, etc.) and also give these descriptive identifiers. (Some people prefer to keep each thread in their own storyboard. This is good practice, IMO).
Another nice additional idea: give your application the launch of the vc ID storyboard too (for example, βLaunchVCβ) so that you can capture it and use it during startup. This will provide a seamless experience for the user during startup and during the execution of your startup tasks.
This is what my LaunchViewController looks like ....
@implementation LaunchViewController - (void)viewDidLoad { [super viewDidLoad]; // optional, but I really like this: // cover my view with my launch screen view for a seamless start UIStoryboard *storyboard = [self.class storyboardWithKey:@"UILaunchStoryboardName"]; UINavigationController *vc = [storyboard instantiateViewControllerWithIdentifier:@"LaunchVC"]; [self.view addSubview:vc.view]; } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; [self hideBusyUI]; } - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; [self showBusyUI]; // start your startup logic here: // let say you need to do a network transaction... // [someNetworkCallingObject doSomeNetworkCallCompletion:^(id result, NSError *error) { if (/* some condition */) [self.class presentUI:@"AppUI"]; else if (/* some condition */) [self.class presentUI:@"LoginUI"]; // etc. }]; }
Original answer below, although I prefer my current approach
Let it express the readiness of the application to launch the main vc with a logical one, something like:
BOOL readyToRun = startupWorkIsDone && userIsLoggedIn;
- Create an AppStartupViewController and place it in the application storyboard.
- Do not drag any segue onto it and do not do it vc ving, just leave it floating somewhere.
- In the vc attribute inspector in the storyboard, set it as "AppStartupViewController".
In AppStartupViewController.m, when the readyToRun conditions are met, it can reject itself:
self.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
Now that the application is becoming active, it can check its readiness for launch and present the AppStartupViewController, if necessary. In AppDelegate.h
- (void)applicationDidBecomeActive:(UIApplication *)application { BOOL readyToRun = startupWorkIsDone && userIsLoggedIn; if (!readyToRun) { UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"MainStoryboard" bundle:nil]; AppStartupViewController *startupVC = [storyboard instantiateViewControllerWithIdentifier:@"AppStartupViewController"]; [self.window.rootViewController presentViewController:startupVC animated:NO completion:nil];
This is basically the answer, but there is one hitch. Unfortunately, the main vc loads (this is good) and receives the message viewWillAppear: (not everything is ok) before the AppStartupViewController is presented. This means that in MainViewController.m you need to add a little extra startup logic:
- (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; if (readyToRun) {
I hope this will be helpful.