59

In iOS 5 I have a Storyboard with a modal view controller, that I would like to display if its the user's first time in the app, after that I would like to skip this view controller.

I set an NSDefault key to handle this but when I check to see if this is set and then use performSegueWithIdentifier to initiate the segue, nothing happens. If i put this segue behind a button it works fine...

adam0101
  • 1,198
  • 2
  • 16
  • 24
  • Have you confirmed that the code is being executed by using a breakpoint in the debugger? It is possible your code is in the wrong location. – Jason Nov 22 '11 at 04:34
  • 2
    Basically that was the crux of the problem that it was being called before the initial view finished loading. – adam0101 Nov 22 '11 at 13:08

12 Answers12

50

I answered a similar question where the developer wanted to show a login screen at the start. I put together some sample code for him that can be downloaded here. The key to solving this problem is calling things at the right time if you want to display this new view controller, you will see in the example you have to use something like this

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];

    UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"MainStoryboard" bundle:nil];
    UIViewController *vc = [storyboard instantiateViewControllerWithIdentifier:@"LoginViewController"];
    [vc setModalPresentationStyle:UIModalPresentationFullScreen];

    [self presentModalViewController:vc animated:YES];
}

I also have an explanation of how segues and storyboards work that you can see here

Scott Sherwood
  • 3,108
  • 25
  • 30
  • 24
    Solution works. But this method kind of shows the other screen before skipping. Is there is a way to avoid this? – carbonr Jan 25 '12 at 19:26
  • 1
    I did something similar without storyboards and when presenting the modal controller I have the animated set to NO, this prevented the underlying view from showing. Haven't tried with a storyboard, ymmv. – mwright Mar 27 '12 at 19:10
  • 10
    Putting it in viewDidAppear seems to result in a brief flash of the underlying view, even if animated:NO is set. Any ideas on how to prevent any flash at all? – radven May 10 '12 at 20:22
  • You can avoid any flash of the underlying view by doing [self presentModalViewController:vc animated:NO] within viewWillAppear rather than viewDidAppear. – Dave T Jun 22 '12 at 09:20
  • 6
    The flash can be avoided by setting `vc.view.hidden = YES`. – Mike Kwan Feb 04 '13 at 18:41
  • 7
    If you set the base view of your view controller to be hidden, you'll get a flash but with a black background instead of the underlying view. – amb Jan 09 '14 at 11:32
  • 3
    This does not seem to be right solution because of the flash. – Shirish Kumar Mar 25 '15 at 21:27
  • The answer by @bearMountain is much better and does not flash. – Ford Davis Dec 08 '15 at 07:37
43

Loading in ViewDidLoad caused "under-layer" to flash. I solved this by loading my Storyboard programmatically. Thus, under Target/Main Storyboard - leave this blank. Then add the following:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];

    // Load Main App Screen
    UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"MainStoryboard" bundle:nil];
    HomeScreenVC *homeScreenVC = [storyboard instantiateInitialViewController];
    self.window.rootViewController = homeScreenVC;
    [self.window makeKeyAndVisible];

    // Load Login/Signup View Controller
    UIViewController *mainLoginVC = [storyboard instantiateViewControllerWithIdentifier:@"MainLoginVC"];
    [mainLoginVC setModalPresentationStyle:UIModalPresentationFullScreen];
    [homeScreenVC presentModalViewController:mainLoginVC animated:NO];

    return YES;
}
bearMountain
  • 3,950
  • 1
  • 36
  • 44
  • 1
    All of the solutions above using viewDidLoad were causing flashes of the initial screen for me - this answer was the only one that solved it – Simon East Jul 09 '13 at 11:38
  • +1. And in iOS 7, use presentViewController:animated:completion:. – Markus Rautopuro Jan 16 '14 at 08:43
  • THERE IS A BETTER OPTION! I appreciate this working solution but let me show you one that does not require to break default (Main) storyboard loading mechanism and involves less code — please, take a look at my answer for this question. – Leon Deriglazov Mar 13 '14 at 06:52
  • If you need a specific VC to be instantiated on the same StoryBoard, you can use instantiateViewControllerWithIdentifier – mcatach Mar 15 '16 at 21:25
23

The problem is you are adding a second view to the hierarchy before the first is fully added. Try putting your code in:

-(void)viewDidAppear:(BOOL)animated{
    [super viewDidAppear:animated];
    // Present your modal from here
}

After [super viewDidAppear] is called you have a fully loaded view to modify.

NJones
  • 27,139
  • 8
  • 70
  • 88
  • 1
    This is where my hangup was; once I moved my code here, worky worky! – Ben Kreeger Jul 30 '12 at 18:59
  • @NJones this is the right answer and should had been marked as it. You show what we, developers love to know: Why?. And then an easy solution. Cheers man. – GoRoS Oct 10 '14 at 07:10
12

There is no principal problem with performing segues in viewDidLoad (after the call to super of course).

The problem is performing segues before the window of the application is made visible. The UIViewController you want to display is part of the main storyboard so it is loaded into memory before the app begins running it's code in the app delegate. In your case, the viewDidLoad is called by iOS before your application window got message: MakeKeyAndVisible.

The important part is the visibility. Performing a segue on a view hierarchy in which the window is not visible does nothing!

You can try to do something like this:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// The window initialized with hidden = YES, so in order to perform the segue we need to set this value to NO.
// After this action, the OS will add the window.rootViewController's view as a subview of the window.
self.window.hidden = NO;

[self.window.rootViewController performSegueWithIdentifier:_IDENTIFIER_ sender:self.window.rootViewController];

// Now that the window is not hidden, we must make it key.
[self.window makeKeyWindow];
return YES;
}
EladWeinson
  • 189
  • 3
  • 9
10

UPDATE: this solution no longer works in iOS 8.

A correct way to solve your problem is to trigger the segue / present modal view controller in applicationDidBecomeActive: app delegate method or in a UIApplicationDidBecomeActiveNotification notification handler.

Apple's documentation actually advises the same:

If your app was previously in the background, you could also use it to refresh your app’s user interface.

This solution has the advantage that it works with Main storyboard loading mechanism so that you don't need to load anything manually and write unnecessary code.

I use this solution successfully on iOS 6.1, 7.0 and 7.1 and it should work on iOS 5 either.

Leon Deriglazov
  • 1,132
  • 9
  • 13
  • 3
    To me, this seems to be the best answer. No flash. No dealing with window visibility. A code sample (or two) would make the answer even better. – ilmiacs Apr 30 '14 at 07:10
  • 1
    I found this to be the cleanest solution. For example, in my app delegate `applicationDidBecomeActive:` I have `[self.window.rootViewController presentViewController:[self.window.rootViewController.storyboard instantiateViewControllerWithIdentifier:@"loginViewController"] animated:NO completion:nil];` – twhitbeck Nov 12 '14 at 17:41
  • 1
    in 8.4 this still caused the initial VC to flash before transitioning. – C6Silver Jul 15 '15 at 18:34
6

For Swift:

dispatch_async(dispatch_get_main_queue()) {
   self.performSegueWithIdentifier("toView2", sender: self)
}

For Swift 3:

DispatchQueue.main.async {
    self.performSegueWithIdentifier("toView2", sender: self)
}
sanmai
  • 29,083
  • 12
  • 64
  • 76
pbaranski
  • 22,778
  • 19
  • 100
  • 117
  • DispatchQueue.main.async { self.performSegue(withIdentifier: "enterSegue", sender: self) } if you need it on Swift 3.0 :) – Dx_ Oct 21 '16 at 15:13
  • Apparently fixes "Unbalanced calls to begin/end appearance transitions" warning – sanmai Sep 04 '17 at 04:19
5

This is how I did it in SWIFT. This also hides the View Controller.

override func viewWillAppear(animated: Bool) {
    let prefs:NSUserDefaults = NSUserDefaults.standardUserDefaults()

    let isloggedIn = prefs.objectForKey("isLoggedIn") as? Bool
    if (isloggedIn != false) {
        self.view.hidden = true
    } else {
        self.view.hidden = false
    }
}

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

    let prefs:NSUserDefaults = NSUserDefaults.standardUserDefaults()

    let isloggedIn = prefs.objectForKey("isLoggedIn") as? Bool
    if (isloggedIn != false) {
        println("this should work")
        self.performSegueWithIdentifier("Login", sender: self)
    }
}
Serge Pedroza
  • 2,160
  • 3
  • 28
  • 41
2

Swift 3

override func viewWillAppear(_ animated: Bool) {
    if authPreference.isExist() == true {
        self.view.isHidden = true
    } else {
        self.view.isHidden = false
    }
}

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

    if authPreference.isExist() == true {
        navigateToSegue()
    }
}
Jaseem Abbas
  • 5,028
  • 6
  • 48
  • 73
1

I had the same problem. Before finding this question, I solved this issue by using async in the main thread. This way, this code will be called by the UI thread right after creating the view.

dispatch_async(dispatch_get_main_queue(), ^{
        [self performSegueWithIdentifier:@"segueAlbums" sender:self];
    });

This code can be called in the viewDidLoad method.

GuillermoMP
  • 1,694
  • 16
  • 19
1

Updated for Swift 3

The code snippet below allows you to load whichever viewController you want. In my case it was a TabBarController if the user had a valid facebook login token. The benefit to this solution over the other Swift 3 solution is that it's instantaneous with no screen flicker.

func applicationDidBecomeActive(_ application: UIApplication) {
    if FBSDKAccessToken.current() != nil {
        self.window?.rootViewController?.present((self.window?.rootViewController?.storyboard?.instantiateViewController(withIdentifier: "TabBarController"))!, animated: false, completion: nil)           
    }
}
James Jordan Taylor
  • 1,560
  • 3
  • 24
  • 38
0

The best solution is to do this:

-(void)viewDidAppear:(BOOL)animated{
    [super viewDidAppear:animated];    
    [self performSegueWithIdentifier:@"NameSegue" sender:self];
}
nhegroj
  • 59
  • 1
  • 3
0

I adapted @bearMountain answer for Swift 3.

func application(_ application: UIApplication,
                     didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    window = UIWindow(frame: UIScreen.main.bounds)
    let yourInitialVC: UIViewController? = storyboard.instantiateViewController(withIdentifier: "TermsVC")
    window?.rootViewController = termsVC
    window?.makeKeyAndVisible()
    return true
}
Nathan Barreto
  • 365
  • 2
  • 21