(Objective-C) Difference between accessing the navigation stack through the window.rootView and keyWindow

75 Views Asked by At

Does anyone know what the difference is between

TabBarController* tabBar = (TabBarController *)_window.rootViewController;
UINavigationController* navigationController = tabBar.selectedViewController;
ViewController* initialViewController = (ViewController *)navigationController.topViewController;

and this

UINavigationController* navigationController = [UIApplication sharedApplication].keyWindow.rootViewController.navigationController;

ViewController* initialViewController = (ViewController *)navigationController.topViewController;

My assumptions:

My results :

  • example A succeeds at pushing my new VC onto the stack while example B doesn't.

Putting aside design principles (example B seems hacky right now, but it is all I have thus far), I don't really understand the difference in these 2 examples, shouldn't they perform the same? Could anyone shine a light on why these are different ?

2

There are 2 best solutions below

0
On BEST ANSWER
  1. The navigationController property on UIViewController is nullable, so it may return nil.

  2. The topViewController property on UINavigationController is also nullable, and returns a UIViewController?, not a specific class. Simply casting any UIViewController to ViewController is not safe, so you should be checking against isKindOfClass.

  3. Same thing applies for rootViewController, it may not always be a TabBarController, so casting it unconditionally is unsafe.

  • also, found out that if rootViewController is of type UITableViewController, the navigationController is nil.
4
On

The navigationController method will return a parent view controller, the nearest parent UINavigationController. In your case, it appears as though the root view controller is a UITabBarController, with a UINavigationController in each tab. Calling -navigationController on the tab controller will return nil, because it has no parent view controller (indeed, it is the root).

If you have multiple windows in your application, the -keyWindow can change. That method is also deprecated in iOS 13, as there can now be multiple window scenes, so if you know the specific window you are in to start with, it's probably better to use that rather than starting from the UIApplication and drilling down.

EDIT: If the goal is to present a new view controller, you would start with the root view controller, and walk up the presented view controllers, and present your new one on top of that.

@implementation UIViewController (MyOvertop)

- (void)my_presentOvertopViewController:(UIViewController *)other animated:(BOOL)animated completion:(void (^ _Nullable)(void))completion
{
    UIViewController *topmost = self;

    while (topmost.presentedViewController &&
           ![topmost.presentedViewController isBeingDismissed])
    {
        topmost = topmost.presentedViewController;
    }

    if (topmost.transitionCoordinator) {
        // transition already going on, wait til it's done and try again
        [topmost.transitionCoordinator animateAlongsideTransition:nil completion:^(id context) {
            dispatch_async(dispatch_get_main_queue(), ^{
                [self my_presentOvertopViewController:vc animated:animated completion:completion];
            });
        }];
        return;
    }

    if (topmost != other) {
        [topmost presentViewController:other animated:animated completion:completion];
    }
}

@end

(might have typos above, but that is the idea)