41

I'm doing the Facebook integration tutorial, I want to show my MainViewViewController if the user has a valid token for the current state otherwise I want to show LoginViewController.

MainViewAppDelegate.m

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    if (FBSession.activeSession.state == FBSessionStateCreatedTokenLoaded) {
        // To-do, show logged in view
    } else {
        // No, display the login page.
        [self showLoginView];
    }
    return YES;
}
- (void)showLoginView
{
    UIStoryboard *mainstoryboard = [UIStoryboard storyboardWithName:@"MainStoryboard"          bundle:nil];
    LoginViewController* loginViewController = [mainstoryboard      instantiateViewControllerWithIdentifier:@"LoginViewController"];
    [self.window.rootViewController presentViewController:loginViewController animated:YES completion:NULL];
}

Console error :

Warning: Attempt to present <LoginViewController: 0xb492fd0> on <MainViewViewController: 0xb1bd820> whose view is not in the window hierarchy!

I don't want to use a NavigationController.

Community
  • 1
  • 1
Boris-V
  • 437
  • 1
  • 4
  • 15

6 Answers6

137

I had the same issue. Based on the answer to this question, I added [self.window makeKeyAndVisible] just before presentViewController:animated:completion:, and that fixed it for me.

In your case, showLoginView becomes

- (void)showLoginView
{
    UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"MainStoryboard" bundle:nil];
    LoginViewController *loginViewController = [storyboard instantiateViewControllerWithIdentifier:@"LoginViewController"];
    [self.window makeKeyAndVisible];
    [self.window.rootViewController presentViewController:loginViewController animated:YES completion:NULL];
}
Community
  • 1
  • 1
shebang
  • 1,655
  • 1
  • 12
  • 12
38

Sometimes presenting modal view controller from window.rootViewController may produce the same warning & have no effect. Example of such hierarchy of view controllers:

  1. [MYUITableViewController] (presented modally by MYUIViewController )
  2. [MYUIViewController] (rootViewController of UINavigationController below)
  3. [UINavigationController] (root)

Now calling

[[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:[UIViewController new] animated:YES completion:nil];

will cause this exact warning (tested both on iOS6 & 7 Sim)

Solution: Instead of using rootViewController - use the top one presented by it:

    UIViewController *topRootViewController = [UIApplication sharedApplication].keyWindow.rootViewController;
    while (topRootViewController.presentedViewController) 
    {
        topRootViewController = topRootViewController.presentedViewController;
    }

    [topRootViewController presentViewController:yourController animated:YES completion:nil];
  • Sometimes keyWindow may have been replaced by window with nil rootViewController (showing UIAlertViews, UIActionSheets on iPhone, etc), in that case you should use UIView's window property.
Stepan Generalov
  • 489
  • 4
  • 10
  • I'm getting "Unbalanced calls to begin/end appearance transitions for " – Slavcho Oct 06 '14 at 13:52
  • Guys, please help, I have a same issue, but can't figure out what's going to be the Swift version? I do this: var sharedApplicationVC : UIViewController! = self.window?.rootViewController while var vvc = sharedApplicationVC.presentedViewController{ sharedApplicationVC = vvc } sharedApplicationVC.presentViewController(closeableVC, animated: true, completion: nil) , but it gives me memory leak issue: may also be helpful. fatal error: unexpectedly found nil while unwrapping an Optional value – Renat Gatin Sep 23 '15 at 23:32
  • 1
    does this work if there is uialertcontroller displaying on the screen and want to present another viewcontroller? –  Oct 01 '15 at 08:34
  • Thank you ! :) Saved my day :) – Adam Studenic Dec 20 '15 at 18:26
  • 1
    Yes! I've try all, but nothing works. This is the correct answer for me. Save my day! Thanks. – TheAlphaGhost Feb 17 '19 at 09:56
4

Stepan Generalov's answer was the right one for me in Swift 3!!!
Of course with the new syntax etc. so I'll copy it in here:

let sb = UIStoryboard(name: "Main", bundle: nil)
let vc = sb.instantiateViewController(withIdentifier: "MainApp") as! ViewController

var topRootViewController: UIViewController = (UIApplication.shared.keyWindow?.rootViewController)!
while((topRootViewController.presentedViewController) != nil){
    topRootViewController = topRootViewController.presentedViewController!
}
topRootViewController.present(vc, animated: true, completion: nil)

"MainApp" is my main view controller's identifier in this case.

I know there are other ways but if you need to have different URL schemes for opening different parts of your App, you must handle it in AppDelegate so this is perfect because in the

func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {}

method, you can just check what url is as a String and then decide if you execute the above written code or maybe a similar one with a different identifier for an other view controller (withIdentifier)

Community
  • 1
  • 1
unixb0y
  • 979
  • 1
  • 10
  • 39
1

In Swift 3 :-

let storyboard = UIStoryboard(name: "Login", bundle: nil)
let viewController = storyboard.instantiateViewController(withIdentifier: "LoginViewController")
window?.makeKeyAndVisible()
window?.rootViewController?.present(viewController, animated: true, completion: nil)
Jay Mehta
  • 1,511
  • 15
  • 20
1

In case, when you're not using a storyboard. First you need to create YourViewController. Go File -> New -> File... (or shortCut - command + N) enter image description here

After that, choose Cocoa Touch Class. Click Next, or Enter enter image description here

Than type name for your viewController and click Next enter image description here

#import "AppDelegate.h"
#import "YourViewController.h"

@interface AppDelegate ()

@end

@implementaion AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

// Init window
self.window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds];
[self.window makeKeyAndVisible];

// Init YourViewController
YourViewController *viewController = [[YourViewController alloc] init];

// Init YourNavigationController
UINavigationController *navigationContoller = [[UINavigationController alloc] initWithRootViewController: viewController];

// Set rootViewController
self.window.rootViewController = navigationContoller;

return YES;

}

@end

Swift 3/4 Example

import UIKit

@UIApplicationMain

class AppDelegate: UIResponder, UIApplicationDelegate {

var window: UIWindow?

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

//1-st step
window = UIWindow(frame: UIScreen.main.bounds)
window?.makeKeyAndVisible()

//2-nd - create a window root controller, and create a layout
let layout = UICollectionViewFlowLayout()
window?.rootViewController = UINavigationController(rootViewController: HomeController(collectionViewLayout: layout))

return true

}

Alex Kolovatov
  • 859
  • 7
  • 12
1

You can launch a new viewController from root like this in Objective-C

UIViewController *presenter = ((AppDelegate *)[[UIApplication sharedApplication] delegate]).window.rootViewController;

[presenter presentViewController:yourViewController animated:YES completion:nil];

Don't forget to add this:

#import "AppDelegate.h"
Sabrina
  • 2,531
  • 1
  • 32
  • 30
  • This won't work unless the rootViewController is in the window hierarchy which isn't always the case. My app got a warning when I tried this. – Brainware Mar 31 '20 at 20:54
  • 1
    @Brainware You can set root view controller in appDelegates didFinishLaunchingWithOptions function, for example : UIViewController *mainVC = [[MainVC alloc] initWithNibName:@"MainXB" bundle:nil]; [self setRootViewController:mainVC]; Or you can define a reference object for your rootViewController and change its value on different scenarios. – Sabrina Apr 02 '20 at 08:37
  • also, Check @Stepan-Generalov solution. I think its completely fine. – Sabrina Apr 02 '20 at 08:44
  • 1
    Thanks. It works fine while showing another controller from AppDelegate. I have integrated it in React Native app, using native modules – PEHLAJ Oct 11 '22 at 06:55