如何从推入式控制器获取 RootViewController?

因此,我从 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 的导航控制器,以及我的方法是否可靠?

146612 次浏览

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

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

这是获取“后台”视图控制器的标准方法。objectAtIndex:0之所以能够工作是因为您试图访问的视图控制器也是根视图控制器,如果您在导航中更深入一些,那么后视图将不会与根视图相同。

快速版:

var rootViewController = self.navigationController?.viewControllers.first

目标 C 版本:

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

Self 是嵌入在 UINavigationController 中的 UIViewController 的实例。

如何向 UIApplication 单身请求它的 KeyWindow,并从 UIWindow请求根视图控制器(它的 RootViewController属性) :

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

几乎所有这些答案中都提到了同一件事的一个稍微不那么丑陋的版本:

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

如果是你,我可能会这么做:

UINavigationController 子类 :

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

然后你可以使用:

UIViewController *rootViewController = [self.navigationController rootViewController];

编辑

OP 在评论中要求一个属性。

如果您愿意,您可以通过类似 self.navigationController.rootViewController的东西来访问它,只需在头部添加一个 readonly 属性:

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

作为对 @ 多尔根的答案的补充,在 objectAtIndex:0上使用 firstObject总是一种好的方法,因为如果数组中没有对象,第一个返回 null,而第二个返回异常。

UIViewController *rootViewController = self.navigationController.rootViewController;

或者,创建一个名为 UINavigationController+Additions的类别并在其中定义方法对您来说也是一个很大的优点。

@interface UINavigationController (Additions)


- (UIViewController *)rootViewController;


@end


@implementation UINavigationController (Additions)


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


@end

对于所有对快速扩展感兴趣的人,以下是我现在使用的:

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

在这里,我想出了一个通用的方法,从任何地方导航到根。

  1. 使用这个类创建一个新的 Class 文件,这样就可以从项目的任何地方访问它:

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

这对我很有效:

当根视图控制器嵌入到导航控制器中时:

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

请记住,keyWindow是不推荐的。

我遇到了一个奇怪的情况。

self.viewControllers.first并不总是 root viewController。

一般来说,self.viewControllers.first确实是 root viewController,但有时它不是。

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 viewController。 但是,当我调用 popToRootViewController(animated:),然后它触发 navigationController(_:willShow:animated:)。此时此刻,self.viewControllers.first不是 root viewController,它是最后一个将消失的 viewController。

摘要

  • self.viewControllers.first并不总是 root viewController。有时候,它会是最后一个 viewController。

因此,我建议当 self.viewControllers只有一个 viewController 时,通过属性保留 rootViewController。我在自定义 UINavigationController 的 viewDidLoad()中得到 root viewController。

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
}


}

环境:

  • IOS14.0.1的 iPhone7
  • Xcode 12.0.1(12A7300)