如何从已推出的控制器中获取RootViewController?

142

所以,我从RootViewController中推一个视图控制器,例如:

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

但是,现在我想从anotherViewController中再次访问RootViewController。

我尝试过:

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

我不确定为什么这样可以,并且也不确定这是否是最好的方法。有人能否评论一下如何更好地从已经被推入到RootViewController的导航控制器中的控制器获取RootViewController,以及我所采用的方法是否可靠?


你所做的将可靠地获取根视图控制器(导航层次结构中的第一个),如果你想要访问“返回”视图控制器,请参考我的答案。 - Ben S
请参阅“设置UIWindow的rootViewController有什么作用?”(https://dev59.com/Tmsy5IYBdhLWcg3w9Svk) - bobobobo
9个回答

185

Swift版本:

var rootViewController = self.navigationController?.viewControllers.first

ObjectiveC版本:

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

其中self是一个嵌入在UINavigationController中的UIViewController实例。


我能否从不同类中获取另一个 ViewController 的 navigationController? - saurabh
任何UIViewController子类都将具有一个navigationController属性,该属性指向其第一个匹配UINavigationController类的父视图控制器。 - dulgan

135

使用UINavigationController的viewControllers属性。示例代码:

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

这是获取“返回”视图控制器的标准方式。 objectAtIndex:0 之所以起作用是因为您要访问的视图控制器也是根视图控制器,如果您在导航中更深层次,则返回视图不会与根视图相同。


6
仍然看起来有点粗糙 - :) 我真的希望有一个“官方”的成员来完成这项工作,比如self.navigationController.rootViewController之类的东西,但是,唉,没有这样的东西。 - bobobobo
62
以上代码有误。rootViewController前面应该加上 *,索引值应该是 0。问题中的代码是正确的:RootViewController *root = (RootViewController *)[navigationController.viewControllers objectAtIndex:0] - ma11hew28
2
同意:上述代码返回的是父级视图控制器,而不是OP所要求的视图控制器。尽管如此,这就是Ben S说他会做的事情,只是他没有足够地指出这一点。 - Ivan Vučica
第二行应该是:UIViewController *rootViewController = [viewControllers objectAtIndex:viewControllers.count - 1]; - Billy

13

这是所有答案中提到的同一件事情的稍微好看一点的版本:

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

在您的情况下,我可能会这样做:
在您的 UINavigationController 子类中:
- (UIViewController *)rootViewController
{
    return [[self viewControllers] firstObject];
}

然后你可以使用:

UIViewController *rootViewController = [self.navigationController rootViewController];

编辑

OP在评论中询问了一个属性。

如果你愿意,你可以通过像self.navigationController.rootViewController这样的方式来访问它,只需在头文件中添加一个只读属性:

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

9

对于所有对Swift扩展感兴趣的人,这是我现在使用的:

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

1
谢谢!您也可以删除“as?UIViewController”。 - atereshkov

3
作为对@dulgan的回答的补充,使用firstObject总是一个好的方法,而不是使用objectAtIndex:0,因为前者如果数组中没有对象则返回nil,后者会抛出异常。
UIViewController *rootViewController = self.navigationController.rootViewController;

或者,你可以创建一个名为UINavigationController+Additions的类别,并在其中定义你的方法,这将是一个很大的优势。

@interface UINavigationController (Additions)

- (UIViewController *)rootViewController;

@end

@implementation UINavigationController (Additions)

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

@end

我刚刚添加了这个部分,没有看你的回答,你说“firstObject”比[0]更好,完全正确。 - dulgan

2
我遇到了一个奇怪的情况。
通常情况下,self.viewControllers.first 确实是根视图控制器。但有时候它并不是。
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)
    }
}

我的问题:

通常情况下,self.viewControllers.firstroot视图控制器。 但是,当我调用popToRootViewController(animated:)时,它会触发navigationController(_:willShow:animated:)。此时,self.viewControllers.first不是根视图控制器,而是将要消失的最后一个视图控制器。

总结

  • self.viewControllers.first并不总是root视图控制器。有时,它将是最后一个视图控制器。

因此,我建议当self.viewControllers仅有一个视图控制器时,通过属性保持rootViewController。我在自定义的UINavigationController的viewDidLoad()中获取根视图控制器。

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
    }

}

环境:

  • iPhone 7,搭载iOS 14.0.1
  • Xcode 12.0.1 (12A7300)

1
我也看到了,旧的遗留代码失败了~!!! - dklt
根视图控制器成为顶部视图控制器。这就是为什么它在navigationController(_, willShow, animated:)中不起作用的原因。 - CiNN
@CiNN,它需要一些测试。通常,“willShow”包含关键字“will”,因此,我猜测视图层次结构应该不会受到苹果API命名风格的影响。 - AechoLiu
1
好的文档说得很清楚,我尝试了一下,它按照文档描述的那样工作。我试图找到一些东西,但没有临时值就无法实现。 - CiNN

1

我想提供一种通用的方法,可以从任何地方导航到根目录。

  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)
        ...
    }
    

1

如何获取导航控制器? - Yestay Muratov
这是一个错误的建议,因为如果我的应用程序的根视图控制器是UITabBarViewController呢? - Sandeep Rana

0

这对我有用:

当我的根视图控制器嵌入导航控制器时:

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

请记住keyWindow已被弃用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接