How do I get the RootViewController from a pushed controller?

IosIphoneUinavigationcontroller

Ios Problem Overview


So, I push a view controller from RootViewController like:

[self.navigationController pushViewController:anotherViewController animated:YES] ;

BUT, FROM anotherViewController now, I want to access the RootViewController again.

I'm trying

// (inside anotherViewController now)
///RootViewController root = (RootViewController)self.parentViewController ; // No.
// err
RootViewController root = (RootViewController)[self.navigationController.viewControllers objectAtIndex:0] ; // YES!! it works

I'm not sure WHY this works and I'm not sure if its the best way to do it. Can somebody comment on a better way to get the RootViewController from a controller you've pushed into that RootViewController's navigationController and whether or not the way I've done it is reliable or not?

Ios Solutions


Solution 1 - Ios

Swift version :

var rootViewController = self.navigationController?.viewControllers.first

ObjectiveC version :

UIViewController *rootViewController = [self.navigationController.viewControllers firstObject];

Where self is an instance of a UIViewController embedded in a UINavigationController.

Solution 2 - Ios

Use the viewControllers property of the UINavigationController. Example code:

// Inside another ViewController
NSArray *viewControllers = self.navigationController.viewControllers;
UIViewController *rootViewController = [viewControllers objectAtIndex:viewControllers.count - 2];

This is the standard way of getting the "back" view controller. The reason objectAtIndex:0 works is because the view controller you're trying to access is also the root one, if you were deeper in the navigation, the back view would not be the same as the root view.

Solution 3 - Ios

A slightly less ugly version of the same thing mentioned in pretty much all these answers:

UIViewController *rootViewController = [[self.navigationController viewControllers] firstObject];

in your case, I'd probably do something like:

inside your UINavigationController subclass:

- (UIViewController *)rootViewController
{
    return [[self viewControllers] firstObject];
}

then you can use:

UIViewController *rootViewController = [self.navigationController rootViewController];

edit

OP asked for a property in the comments.

if you like, you can access this via something like self.navigationController.rootViewController by just adding a readonly property to your header:

@property (nonatomic, readonly, weak) UIViewController *rootViewController;

Solution 4 - Ios

For all who are interested in a swift extension, this is what I'm using now:

extension UINavigationController {
    var rootViewController : UIViewController? {
        return self.viewControllers.first
    }
}

Solution 5 - Ios

As an addition to @dulgan's answer, it is always a good approach to use firstObject over objectAtIndex:0, because while first one returns nil if there is no object in the array, latter one throws exception.

UIViewController *rootViewController = self.navigationController.rootViewController;

Alternatively, it'd be a big plus for you to create a category named UINavigationController+Additions and define your method in that.

@interface UINavigationController (Additions)

- (UIViewController *)rootViewController;

@end

@implementation UINavigationController (Additions)

- (UIViewController *)rootViewController
{
    return self.viewControllers.firstObject;
}

@end

Solution 6 - Ios

I encounter a strange condition.

self.viewControllers.first is not root viewController always.

Generally, self.viewControllers.first is root viewController indeed. But sometimes it's not.

class MyCustomMainNavigationController: UINavigationController {

    function configureForView(_ v: UIViewController, animated: Bool) {
        let root = self.viewControllers.first
        let isRoot = (v == root)
    
        // Update UI based on isRoot
        // ....
    }
}

extension MyCustomMainNavigationController: UINavigationControllerDelegate {
    func navigationController(_ navigationController: UINavigationController, 
        willShow viewController: UIViewController, 
        animated: Bool) {

        self.configureForView(viewController, animated: animated)
    }
}
My issue:

Generally, self.viewControllers.first is root viewController. But, when I call popToRootViewController(animated:), and then it triggers navigationController(_:willShow:animated:). At this moment, self.viewControllers.first is NOT root viewController, it's the last viewController which will disappear.

Summary
  • self.viewControllers.first is not always root viewController. Sometime, it will be the last viewController.

So, I suggest to keep rootViewController by property when self.viewControllers have ONLY one viewController. I get root viewController in viewDidLoad() of custom UINavigationController.

class MyCustomMainNavigationController: UINavigationController {
    fileprivate var myRoot: UIViewController!
    override func viewDidLoad() {

        super.viewDidLoad()

        // My UINavigationController is defined in storyboard. 
        // So at this moment, 
        // I can get root viewController by `self.topViewController!`
        let v = self.topViewController!
        self.myRoot = v
    }

}
Enviroments:
  • iPhone 7 with iOS 14.0.1
  • Xcode 12.0.1 (12A7300)

Solution 7 - Ios

How about asking the UIApplication singleton for its keyWindow, and from that UIWindow ask for the root view controller (its rootViewController property):

UIViewController root = [[[UIApplication sharedApplication] keyWindow] rootViewController];

Solution 8 - Ios

Here I came up with universal method to navigate from any place to root.

  1. You create a new Class file with this class, so that it's accessible from anywhere in your project:

     import UIKit
    
     class SharedControllers
     {
         static func navigateToRoot(viewController: UIViewController)
         {
             var nc = viewController.navigationController
             
             // If this is a normal view with NavigationController, then we just pop to root.
             if nc != nil
             {
                 nc?.popToRootViewControllerAnimated(true)
                 return
             }
             
             // Most likely we are in Modal view, so we will need to search for a view with NavigationController.
             let vc = viewController.presentingViewController
             
             if nc == nil
             {
                 nc = viewController.presentingViewController?.navigationController
             }
             
             if nc == nil
             {
                 nc = viewController.parentViewController?.navigationController
             }
             
             if vc is UINavigationController && nc == nil
             {
                 nc = vc as? UINavigationController
             }
             
             if nc != nil
             {
                 viewController.dismissViewControllerAnimated(false, completion:
                     {
                         nc?.popToRootViewControllerAnimated(true)
                 })
             }
         }
     }
    
  2. Usage from anywhere in your project:

     {
         ...
         SharedControllers.navigateToRoot(self)
         ...
     }
    

Solution 9 - Ios

This worked for me:

When my root view controller is embedded in a navigation controller:

UINavigationController * navigationController = (UINavigationController *)[[[[UIApplication sharedApplication] windows] firstObject] rootViewController];
RootViewController * rootVC = (RootViewController *)[[navigationController viewControllers] firstObject];

Remember that keyWindow is deprecated.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionboboboboView Question on Stackoverflow
Solution 1 - IosdulganView Answer on Stackoverflow
Solution 2 - IosBen SView Answer on Stackoverflow
Solution 3 - IosJesseView Answer on Stackoverflow
Solution 4 - IoscschView Answer on Stackoverflow
Solution 5 - IosOzgur VatanseverView Answer on Stackoverflow
Solution 6 - IosAechoLiuView Answer on Stackoverflow
Solution 7 - IosBasil BourqueView Answer on Stackoverflow
Solution 8 - IosMarisView Answer on Stackoverflow
Solution 9 - IosPedro TrujilloView Answer on Stackoverflow