如何在iOS上找到最顶层的视图控制器

285

我遇到了几种情况,需要找到“最上层”的视图控制器(负责当前视图的视图控制器),但没有找到方法。

基本上,这个挑战是这样的:假设一个类不是视图控制器(或视图)[并且没有活动视图的地址],也没有传入最上面视图控制器的地址(或者比如导航控制器的地址),那么是否可能找到该视图控制器呢?(如果可以,怎么实现?)

或者,如果找不到它,是否可能找到最上层的视图?


那么你的意思是说这是不可能的。 - Hot Licks
@Daniel 不,我的意思是你的代码似乎需要重新设计,因为你很少需要知道这个。此外,“最顶层”的概念只在特定情况下有效,即使在那种情况下也不总是有效。 - Dave DeLong
@Daniel,我误读了您的问题。 尝试回答此问题时有很多情况需要考虑。这取决于您的视图控制器流程。 @Wilbur的答案应该是追踪它的好起点。 - Deepak Danduprolu
4
@Daniel:添加第二个UIWindow 对于警报视图类似的覆盖效果很有效。 - Wilbur Vandrsmith
是的,UIWindow似乎是实现警报的方法。这并不能解决找出最上层视图控制器(想要将其放在顶部的位置)的另一个问题,但我想这些问题需要分别处理。 - Hot Licks
显示剩余2条评论
42个回答

10

这是对Eric答案的改进:

UIViewController *_topMostController(UIViewController *cont) {
    UIViewController *topController = cont;

    while (topController.presentedViewController) {
        topController = topController.presentedViewController;
    }

    if ([topController isKindOfClass:[UINavigationController class]]) {
        UIViewController *visible = ((UINavigationController *)topController).visibleViewController;
        if (visible) {
            topController = visible;
        }
    }

    return (topController != cont ? topController : nil);
}

UIViewController *topMostController() {
    UIViewController *topController = [UIApplication sharedApplication].keyWindow.rootViewController;

    UIViewController *next = nil;

    while ((next = _topMostController(topController)) != nil) {
        topController = next;
    }

    return topController;
}

_topMostController(UIViewController *cont)是一个帮助函数。

现在你只需要调用topMostController()函数,便可以返回最顶层的UIViewController!


7
自1983年以来,我会这么说。请记住,Objective-C包含C语言...将ObjC代码封装在C函数中是一种常见的做法,所以没错,这是Objective-C代码。 - JonasG
@JonasG 你好 Jonas,您在什么情况下更喜欢用 C 包装 ObjC 代码?因为我有时候会看到像这样的 C 函数,无法区分用途。将代码包装在 C 中是否提供任何性能优势? - OzBoz
1
在不清楚self应该属于哪个类的情况下。 - adib

9
@implementation UIWindow (扩展)
- (UIViewController*) topMostController { // 获取顶层控制器 UIViewController *topController = [self rootViewController];
while (topController.presentedViewController) { topController = topController.presentedViewController; }
return topController; }
@end

1
我认为你没有满足原帖中所述的条件。 - Hot Licks

8
这是我的观点。感谢@Stakenborg指出了跳过获取UIAlertView作为最顶层控制器的方法。
-(UIWindow *) returnWindowWithWindowLevelNormal
{
    NSArray *windows = [UIApplication sharedApplication].windows;
    for(UIWindow *topWindow in windows)
    {
        if (topWindow.windowLevel == UIWindowLevelNormal)
            return topWindow;
    }
    return [UIApplication sharedApplication].keyWindow;
}

-(UIViewController *) getTopMostController
{
    UIWindow *topWindow = [UIApplication sharedApplication].keyWindow;
    if (topWindow.windowLevel != UIWindowLevelNormal)
    {
        topWindow = [self returnWindowWithWindowLevelNormal];
    }

    UIViewController *topController = topWindow.rootViewController;
    if(topController == nil)
    {
        topWindow = [UIApplication sharedApplication].delegate.window;
        if (topWindow.windowLevel != UIWindowLevelNormal)
        {
            topWindow = [self returnWindowWithWindowLevelNormal];
        }
        topController = topWindow.rootViewController;
    }

    while(topController.presentedViewController)
    {
        topController = topController.presentedViewController;
    }

    if([topController isKindOfClass:[UINavigationController class]])
    {
        UINavigationController *nav = (UINavigationController*)topController;
        topController = [nav.viewControllers lastObject];

        while(topController.presentedViewController)
        {
            topController = topController.presentedViewController;
        }
    }

    return topController;
}

在Objective-C中,你应该避免将方法命名为 getSomething:。这个名称有特殊的含义(更多信息请参见http://cocoadevcentral.com/articles/000082.php),而你的代码并未满足这些要求。 - Nat

8

这是一个关于UIApplication的简单扩展,用Swift编写。

注意:

它关心UITabBarController中的moreNavigationController

extension UIApplication {

    class func topViewController(baseViewController: UIViewController? = UIApplication.sharedApplication().keyWindow?.rootViewController) -> UIViewController? {

        if let navigationController = baseViewController as? UINavigationController {
            return topViewController(navigationController.visibleViewController)
        }

        if let tabBarViewController = baseViewController as? UITabBarController {

            let moreNavigationController = tabBarViewController.moreNavigationController

            if let topViewController = moreNavigationController.topViewController where topViewController.view.window != nil {
                return topViewController(topViewController)
            } else if let selectedViewController = tabBarViewController.selectedViewController {
                return topViewController(selectedViewController)
            }
        }

        if let splitViewController = baseViewController as? UISplitViewController where splitViewController.viewControllers.count == 1 {
            return topViewController(splitViewController.viewControllers[0])
        }

        if let presentedViewController = baseViewController?.presentedViewController {
            return topViewController(presentedViewController)
        }

        return baseViewController
    }
}

简单用法:
if let topViewController = UIApplication.topViewController() {
    //do sth with top view controller
}

是的,是的,是的 - 在网络上有许多解决方案可以找到最顶层的视图控制器,但如果您的应用程序带有一个“更多”选项卡的标签栏,您必须稍微处理一下。 - Andy Obusek

7
- (UIViewController*)topViewController {
    return [self topViewControllerWithRootViewController:[UIApplication sharedApplication].keyWindow.rootViewController];
}

- (UIViewController*)topViewControllerWithRootViewController:(UIViewController*)rootViewController {
    if ([rootViewController isKindOfClass:[UITabBarController class]]) {
        UITabBarController* tabBarController = (UITabBarController*)rootViewController;
        return [self topViewControllerWithRootViewController:tabBarController.selectedViewController];
    } else if ([rootViewController isKindOfClass:[UINavigationController class]]) {
        UINavigationController* navigationController = (UINavigationController*)rootViewController;
        return [self topViewControllerWithRootViewController:navigationController.visibleViewController];
    } else if (rootViewController.presentedViewController) {
        UIViewController* presentedViewController = rootViewController.presentedViewController;
        return [self topViewControllerWithRootViewController:presentedViewController];
    } else {
        return rootViewController;
    }
}

我使用了这个,但请注意当出现多个展示视图控制器时,它会中断。 - Chuck Boris

6

Swift 4.2 扩展


extension UIApplication {

    class func topViewController(controller: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
        if let navigationController = controller as? UINavigationController {
            return topViewController(controller: navigationController.visibleViewController)
        }
        if let tabController = controller as? UITabBarController {
            if let selected = tabController.selectedViewController {
                return topViewController(controller: selected)
            }
        }
        if let presented = controller?.presentedViewController {


            return topViewController(controller: presented)
        }
        return controller
    }
}

可以从任何地方使用它,例如:

 UIApplication.topViewController()?.present(yourController, animated: true, completion: nil)

或者像这样:
 UIApplication.topViewController()?
                    .navigationController?
                    .popToViewController(yourController,
                                         animated: true)

适用于任何类似于UINavigationController、UITabBarController的类

享受吧!


1
@BilalBakhrom说:“点赞。我认为你的答案是最好的。你不能直接调用topViewController()方法。UIApplication类是单例,使用一个名为“shared”的实例。”在我投票拒绝的编辑中。如果这确实正确,请点击此处进入菜单,您可以批准该编辑。 - wizzwizz4
“keyWindow”在iOS 13.0中已被弃用:不应该用于支持多个场景的应用程序,因为它会返回所有连接场景中的关键窗口。 - Carmen

5

以下是Swift 4.2 中简洁而全面的解决方案,考虑了 UINavigationControllersUITabBarControllerspresentedchild 视图控制器:

extension UIViewController {
  func topmostViewController() -> UIViewController {
    if let navigationVC = self as? UINavigationController,
      let topVC = navigationVC.topViewController {
      return topVC.topmostViewController()
    }
    if let tabBarVC = self as? UITabBarController,
      let selectedVC = tabBarVC.selectedViewController {
      return selectedVC.topmostViewController()
    }
    if let presentedVC = presentedViewController {
      return presentedVC.topmostViewController()
    }
    if let childVC = children.last {
      return childVC.topmostViewController()
    }
    return self
  }
}

extension UIApplication {
  func topmostViewController() -> UIViewController? {
    return keyWindow?.rootViewController?.topmostViewController()
  }
}

使用方法:

let viewController = UIApplication.shared.topmostViewController()

4
另一种Swift解决方案
func topController() -> UIViewController? {

    // recursive follow
    func follow(from:UIViewController?) -> UIViewController? {
        if let to = (from as? UITabBarController)?.selectedViewController {
            return follow(to)
        } else if let to = (from as? UINavigationController)?.visibleViewController {
            return follow(to)
        } else if let to = from?.presentedViewController {
            return follow(to)
        }
        return from
    }

    let root = UIApplication.sharedApplication().keyWindow?.rootViewController

    return follow(root)

}

3

另一种Swift解决方案:

static func topMostController() -> UIViewController {
    var topController = UIApplication.sharedApplication().keyWindow?.rootViewController
    while (topController?.presentedViewController != nil) {
        topController = topController?.presentedViewController
    }

    return topController!
}

3

以下是我成功尝试的方法。

我发现有时候控制器在关键窗口上是空的,因为关键窗口是一些类似于警告等操作系统的东西。

 + (UIViewController*)topMostController
 {
     UIWindow *topWndow = [UIApplication sharedApplication].keyWindow;
     UIViewController *topController = topWndow.rootViewController;

     if (topController == nil)
     {
         // The windows in the array are ordered from back to front by window level; thus,
         // the last window in the array is on top of all other app windows.
         for (UIWindow *aWndow in [[UIApplication sharedApplication].windows reverseObjectEnumerator])
         {
             topController = aWndow.rootViewController;
             if (topController)
                 break;
         }
     }

     while (topController.presentedViewController) {
         topController = topController.presentedViewController;
     }

     return topController;
 }

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