How do I get the RootViewController from a pushed controller?
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 = (RootViewCo开发者_如何学Gontroller*)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?
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.
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.
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;
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
}
}
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
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 alwaysroot
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)
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];
Here I came up with universal method to navigate from any place to root.
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) }) } } }
Usage from anywhere in your project:
{ ... SharedControllers.navigateToRoot(self) ... }
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.
精彩评论