iPhone – 如何find最顶层的视图控制器

现在我遇到了一些情况,能够方便地find“最顶层”的视图控制器(负责当前视图的控制器),但是还没有find办法。

基本上,挑战是这样的:假定一个在不是视图控制器 (或视图) 的类中执行并且没有活动视图的地址)并且尚未传递最顶层视图控制器的地址(或者说,导航控制器的地址),是否有可能find该视图控制器? (如果是这样,怎么样?)

或者,如果失败了,是否有可能find最顶层的视图?

iOS 4在UIWindow中引入了rootViewController属性:

[UIApplication sharedApplication].keyWindow.rootViewController; 

你需要在创build视图控制器后自行设置它。

我认为你需要接受的答案和@ fishstix的组合

 + (UIViewController*) topMostController { UIViewController *topController = [UIApplication sharedApplication].keyWindow.rootViewController; while (topController.presentedViewController) { topController = topController.presentedViewController; } return topController; } 

Swift 3.0+

 let rootViewController = UIApplication.shared.keyWindow?.rootViewController 

为了完成JonasG的回答 (在遍历时没有标签栏控制器),这里是我返回当前可见视图控制器的版本:

 - (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; } } 

为了完成Eric的回答 (删除了popover,导航控制器,tabbarcontrollers,视图控制器作为子视图添加到一些其他视图控制器,而遍历),这里是我的版本返回当前可见的视图控制器:

================================================== ===================

 - (UIViewController*)topViewController { return [self topViewControllerWithRootViewController:[UIApplication sharedApplication].keyWindow.rootViewController]; } - (UIViewController*)topViewControllerWithRootViewController:(UIViewController*)viewController { if ([viewController isKindOfClass:[UITabBarController class]]) { UITabBarController* tabBarController = (UITabBarController*)viewController; return [self topViewControllerWithRootViewController:tabBarController.selectedViewController]; } else if ([viewController isKindOfClass:[UINavigationController class]]) { UINavigationController* navContObj = (UINavigationController*)viewController; return [self topViewControllerWithRootViewController:navContObj.visibleViewController]; } else if (viewController.presentedViewController && !viewController.presentedViewController.isBeingDismissed) { UIViewController* presentedViewController = viewController.presentedViewController; return [self topViewControllerWithRootViewController:presentedViewController]; } else { for (UIView *view in [viewController.view subviews]) { id subViewController = [view nextResponder]; if ( subViewController && [subViewController isKindOfClass:[UIViewController class]]) { if ([(UIViewController *)subViewController presentedViewController] && ![subViewController presentedViewController].isBeingDismissed) { return [self topViewControllerWithRootViewController:[(UIViewController *)subViewController presentedViewController]]; } } } return viewController; } } 

================================================== ===================

现在,您只需要获得最顶层的视图控制器就可以调用上述方法,如下所示:

 UIViewController *topMostViewControllerObj = [self topMostViewController]; 

使用扩展获取Swift最顶级的视图控制器

码:

 extension UIViewController { @objc func topMostViewController() -> UIViewController { // Handling Modal views if let presentedViewController = self.presentedViewController { return presentedViewController.topMostViewController() } // Handling UIViewController's added as subviews to some other views. else { for view in self.view.subviews { // Key property which most of us are unaware of / rarely use. if let subViewController = view.next { if subViewController is UIViewController { let viewController = subViewController as! UIViewController return viewController.topMostViewController() } } } return self } } } extension UITabBarController { override func topMostViewController() -> UIViewController { return self.selectedViewController!.topMostViewController() } } extension UINavigationController { override func topMostViewController() -> UIViewController { return self.visibleViewController!.topMostViewController() } } 

用法:

 UIApplication.sharedApplication().keyWindow!.rootViewController!.topMostViewController() 

这个答案包括childViewControllers和维护一个干净和可读的实现。

 + (UIViewController *)topViewController { UIViewController *rootViewController = [UIApplication sharedApplication].keyWindow.rootViewController; return [rootViewController topVisibleViewController]; } - (UIViewController *)topVisibleViewController { if ([self isKindOfClass:[UITabBarController class]]) { UITabBarController *tabBarController = (UITabBarController *)self; return [tabBarController.selectedViewController topVisibleViewController]; } else if ([self isKindOfClass:[UINavigationController class]]) { UINavigationController *navigationController = (UINavigationController *)self; return [navigationController.visibleViewController topVisibleViewController]; } else if (self.presentedViewController) { return [self.presentedViewController topVisibleViewController]; } else if (self.childViewControllers.count > 0) { return [self.childViewControllers.lastObject topVisibleViewController]; } return self; } 

一个完整的非recursion版本,照顾不同的情况:

  • 视图控制器正在呈现另一个视图
  • 视图控制器是一个UINavigationController
  • 视图控制器是一个UITabBarController

Objective-C的

  UIViewController *topViewController = self.window.rootViewController; while (true) { if (topViewController.presentedViewController) { topViewController = topViewController.presentedViewController; } else if ([topViewController isKindOfClass:[UINavigationController class]]) { UINavigationController *nav = (UINavigationController *)topViewController; topViewController = nav.topViewController; } else if ([topViewController isKindOfClass:[UITabBarController class]]) { UITabBarController *tab = (UITabBarController *)topViewController; topViewController = tab.selectedViewController; } else { break; } } 

Swift 4和3.1

 extension UIWindow { func topViewController() -> UIViewController? { var top = self.rootViewController while true { if let presented = top?.presentedViewController { top = presented } else if let nav = top as? UINavigationController { top = nav.visibleViewController } else if let tab = top as? UITabBarController { top = tab.selectedViewController } else { break } } return top } } 

我最近在我的项目中遇到了这种情况,当networking状态改变时,无论显示的控制器是什么,无论是哪种types(UINavigationController,经典控制器或自定义视图控制器),都需要显示通知视图。

所以我正确地发布了我的代码,这很容易,而且实际上是基于一个协议,所以对于每一种容器控制器都是很灵活的。 这似乎与最后的答案有关,但以一种非常灵活的方式。

你可以在这里获取代码: PPTopMostController

并得到了最顶级的控制器使用

 UIViewController *c = [UIViewController topMostController]; 

这是对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应该返回!

 - (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; } } 

最新的Swift版本:
创build一个文件,将其命名为UIWindowExtension.swift并粘贴下面的代码片段:

 import UIKit public extension UIWindow { public var visibleViewController: UIViewController? { return UIWindow.getVisibleViewControllerFrom(self.rootViewController) } public static func getVisibleViewControllerFrom(vc: UIViewController?) -> UIViewController? { if let nc = vc as? UINavigationController { return UIWindow.getVisibleViewControllerFrom(nc.visibleViewController) } else if let tc = vc as? UITabBarController { return UIWindow.getVisibleViewControllerFrom(tc.selectedViewController) } else { if let pvc = vc?.presentedViewController { return UIWindow.getVisibleViewControllerFrom(pvc) } else { return vc } } } } func getTopViewController() -> UIViewController? { let appDelegate = UIApplication.sharedApplication().delegate if let window = appDelegate!.window { return window?.visibleViewController } return nil } 

使用它在任何地方:

 if let topVC = getTopViewController() { } 
 @implementation UIWindow(Extensions)

 - (UIViewController *)topMostController
 {
     UIViewController * topController = [self rootViewController];

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

    返回topController;
 }

 @结束

这是我的承诺。 感谢@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; } 

Swift中UIApplication简单扩展:

注意:

它关心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 } 

这是为我工作。

我发现有时候控制器在关键窗口是零,因为keyWindow是一些操作系统,比如警报等等。

  + (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; } 

扩展@ Eric的答案,你需要小心keyWindow实际上是你想要的窗口。 例如,如果您尝试在警报视图中点击某些内容后使用此方法,那么keyWindow实际上就是警报的窗口,这将毫无疑问地为您带来问题。 通过警报处理深层链接时,这种情况在我身上发生,并导致无堆栈跟踪的SIGABRT。 总婊子debugging。

这里是我现在使用的代码:

 - (UIViewController *)getTopMostViewController { UIWindow *topWindow = [UIApplication sharedApplication].keyWindow; if (topWindow.windowLevel != UIWindowLevelNormal) { NSArray *windows = [UIApplication sharedApplication].windows; for(topWindow in windows) { if (topWindow.windowLevel == UIWindowLevelNormal) break; } } UIViewController *topViewController = topWindow.rootViewController; while (topViewController.presentedViewController) { topViewController = topViewController.presentedViewController; } return topViewController; } 

随意与这个问题的其他答案检索您喜欢的顶视图控制器的任何味道混合。

替代Swift解决scheme:

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

又一个Swift解决scheme

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

这个解决scheme是最完整的。 它需要考虑:UINavigationController UIPageViewController UITabBarController最顶层的视图控制器从顶视图控制器

这个例子在Swift 3中。

有3个重载

 //Get the topmost view controller for the current application. public func MGGetTopMostViewController() -> UIViewController? { if let currentWindow:UIWindow = UIApplication.shared.keyWindow { return MGGetTopMostViewController(fromWindow: currentWindow) } return nil } //Gets the topmost view controller from a specific window. public func MGGetTopMostViewController(fromWindow window:UIWindow) -> UIViewController? { if let rootViewController:UIViewController = window.rootViewController { return MGGetTopMostViewController(fromViewController: rootViewController) } return nil } //Gets the topmost view controller starting from a specific UIViewController //Pass the rootViewController into this to get the apps top most view controller public func MGGetTopMostViewController(fromViewController viewController:UIViewController) -> UIViewController { //UINavigationController if let navigationViewController:UINavigationController = viewController as? UINavigationController { let viewControllers:[UIViewController] = navigationViewController.viewControllers if navigationViewController.viewControllers.count >= 1 { return MGGetTopMostViewController(fromViewController: viewControllers[viewControllers.count - 1]) } } //UIPageViewController if let pageViewController:UIPageViewController = viewController as? UIPageViewController { if let viewControllers:[UIViewController] = pageViewController.viewControllers { if viewControllers.count >= 1 { return MGGetTopMostViewController(fromViewController: viewControllers[0]) } } } //UITabViewController if let tabBarController:UITabBarController = viewController as? UITabBarController { if let selectedViewController:UIViewController = tabBarController.selectedViewController { return MGGetTopMostViewController(fromViewController: selectedViewController) } } //Lastly, Attempt to get the topmost presented view controller var presentedViewController:UIViewController! = viewController.presentedViewController var nextPresentedViewController:UIViewController! = presentedViewController?.presentedViewController //If there is a presented view controller, get the top most prensentedViewController and return it. if presentedViewController != nil { while nextPresentedViewController != nil { //Set the presented view controller as the next one. presentedViewController = nextPresentedViewController //Attempt to get the next presented view controller nextPresentedViewController = presentedViewController.presentedViewController } return presentedViewController } //If there is no topmost presented view controller, return the view controller itself. return viewController } 

Swift中的很好的解决scheme,在AppDelegate中实现

 func getTopViewController()->UIViewController{ return topViewControllerWithRootViewController(UIApplication.sharedApplication().keyWindow!.rootViewController!) } func topViewControllerWithRootViewController(rootViewController:UIViewController)->UIViewController{ if rootViewController is UITabBarController{ let tabBarController = rootViewController as! UITabBarController return topViewControllerWithRootViewController(tabBarController.selectedViewController!) } if rootViewController is UINavigationController{ let navBarController = rootViewController as! UINavigationController return topViewControllerWithRootViewController(navBarController.visibleViewController) } if let presentedViewController = rootViewController.presentedViewController { return topViewControllerWithRootViewController(presentedViewController) } return rootViewController } 

不知道这是否会帮助你find最顶层的视图控制器,但我试图提出一个新的视图控制器,但如果我的根视图控制器已经有一个模式对话框,将被阻止,所以我会使用下面的代码循环到所有模态视图控制器的顶部:

 UIViewController* parentController =[UIApplication sharedApplication].keyWindow.rootViewController; while( parentController.presentedViewController && parentController != parentController.presentedViewController ) { parentController = parentController.presentedViewController; } 

迅速:

 extension UIWindow { func visibleViewController() -> UIViewController? { if let rootViewController: UIViewController = self.rootViewController { return UIWindow.getVisibleViewControllerFrom(rootViewController) } return nil } class func getVisibleViewControllerFrom(vc:UIViewController) -> UIViewController { if vc.isKindOfClass(UINavigationController.self) { let navigationController = vc as UINavigationController return UIWindow.getVisibleViewControllerFrom( navigationController.visibleViewController) } else if vc.isKindOfClass(UITabBarController.self) { let tabBarController = vc as UITabBarController return UIWindow.getVisibleViewControllerFrom(tabBarController.selectedViewController!) } else { if let presentedViewController = vc.presentedViewController { return UIWindow.getVisibleViewControllerFrom(presentedViewController.presentedViewController!) } else { return vc; } } } 

用法:

  if let topController = window.visibleViewController() { println(topController) } 

下面两个函数可以帮助在堆栈视图控制器上findtopViewController。 稍后您可能需要定制,但是对于此代码来说,理解topViewController或viewController堆栈的概念是非常棒的。

 - (UIViewController*)findTopViewController { id topControler = [self topMostController]; UIViewController* topViewController; if([topControler isKindOfClass:[UINavigationController class]]) { topViewController = [[(UINavigationController*)topControler viewControllers] lastObject]; } else if ([topControler isKindOfClass:[UITabBarController class]]) { //Here you can get reference of top viewcontroller from stack of viewcontrollers on UITabBarController } else { //topController is a preented viewController topViewController = (UIViewController*)topControler; } //NSLog(@"Top ViewController is: %@",NSStringFromClass([topController class])); return topViewController; } - (UIViewController*)topMostController { UIViewController *topController = [UIApplication sharedApplication].keyWindow.rootViewController; while (topController.presentedViewController) { topController = topController.presentedViewController; } //NSLog(@"Top View is: %@",NSStringFromClass([topController class])); return topController; } 

你可以使用[viewController Class]方法来找出viewController类的types。

另一个Swift解决scheme

 extension UIViewController { static var topmostViewController: UIViewController? { return UIApplication.sharedApplication().keyWindow?.topmostViewController } var topmostViewController: UIViewController? { return presentedViewController?.topmostViewController ?? self } } extension UINavigationController { override var topmostViewController: UIViewController? { return visibleViewController?.topmostViewController } } extension UITabBarController { override var topmostViewController: UIViewController? { return selectedViewController?.topmostViewController } } extension UIWindow { var topmostViewController: UIViewController? { return rootViewController?.topmostViewController } } 

如果根控制器是导航控制器,则find顶部可见控制器的正确方法是:

 UIViewController *rootVC = [[UIApplication sharedApplication] keyWindow].rootViewController; if ([rootVC respondsToSelector:@selector(visibleViewController)]) { UIViewController *topVC = [(UINavigationController *)rootVC visibleViewController]; // do your thing with topVC } 

这里是UINavigationController.h的摘录:

 @property(nonatomic,readonly,retain) UIViewController *topViewController; // The top view controller on the stack. @property(nonatomic,readonly,retain) UIViewController *visibleViewController; // Return modal view controller if it exists. Otherwise the top view controller. 

这非常适合从任何根视图控制器中查找顶部viewController 1

 + (UIViewController *)topViewControllerFor:(UIViewController *)viewController { if(!viewController.presentedViewController) return viewController; return [MF5AppDelegate topViewControllerFor:viewController.presentedViewController]; } /* View Controller for Visible View */ AppDelegate *app = [UIApplication sharedApplication].delegate; UIViewController *visibleViewController = [AppDelegate topViewControllerFor:app.window.rootViewController]; 

你可以通过使用find最顶级的视图控制器

 NSArray *arrViewControllers=[[self navigationController] viewControllers]; UIViewController *topMostViewController=(UIViewController *)[arrViewControllers objectAtIndex:[arrViewControllers count]-1]; 

另一个解决scheme依赖于响应者链,根据第一响应者是什么可能或不可以工作。

  1. 获得第一响应者 。
  2. 获取与第一个响应者关联的UIViewController 。

伪代码示例:

 + (UIViewController *)currentViewController { UIView *firstResponder = [self firstResponder]; // from the first link above, but not guaranteed to return a UIView, so this should be handled more appropriately. UIViewController *viewController = [firstResponder viewController]; // from the second link above return viewController; } 

To avoid a lot of complexity I keep track of the current viewController by creating a viewController in the delegate and set it to self inside each viewDidLoad method, this way anytime you load a new view the ViewController held in the delegate will correspond to that view's viewController. This may be ugly, but it works wonderfully, and theres no need to have a navigation controller or any of that nonsense.

Previous answer does not seems to handle cases where rootController are UITabBarController or UINavigationController.

Here is the function in swift which works for those cases :

 func getCurrentView() -> UIViewController? { if let window = UIApplication.sharedApplication().keyWindow, var currentView: UIViewController = window.rootViewController { while (currentView.presentedViewController != nil) { if let presented = currentView.presentedViewController { currentView = presented } } if currentView is UITabBarController { if let visible = (currentView as! UITabBarController).selectedViewController { currentView = visible; } } if currentView is UINavigationController { if let visible = (currentView as! UINavigationController).visibleViewController { currentView = visible; } } return currentView } return nil }