关于C#:如何在iOS上找到最顶层的视图控制器

How to find topmost view controller on iOS

我现在遇到了几个这样的情况:找到"最顶层"的视图控制器(负责当前视图的控制器)很方便,但还没有找到一种方法。

基本上,挑战是:假设一个在一个不是视图控制器(或视图)的类中执行[并且没有活动视图的地址],并且没有传递到最顶层视图控制器的地址(或者说,导航控制器的地址),是否可以找到该视图控制器?(如果是,怎么做?)

或者,如果失败,是否可以找到最顶层的视图?


我想你需要一个公认的答案和@fishstix's的组合。

1
2
3
4
5
6
7
8
9
10
+ (UIViewController*) topMostController
{
    UIViewController *topController = [UIApplication sharedApplication].keyWindow.rootViewController;

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

    return topController;
}

斯威夫特3 +

1
2
3
4
5
6
7
8
9
10
11
12
13
func topMostController() -> UIViewController? {
    guard let window = UIApplication.shared.keyWindow, let rootViewController = window.rootViewController else {
        return nil
    }

    var topController = rootViewController

    while let newTopController = topController.presentedViewController {
        topController = newTopController
    }

    return topController
}


要完成jonasg的回答(遍历时谁遗漏了选项卡栏控制器),这里是我返回当前可见视图控制器的版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- (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;
    }
}


iOS 4在uiwindow上引入了rootviewcontroller属性:

1
[UIApplication sharedApplication].keyWindow.rootViewController;

不过,在创建视图控制器之后,您需要自己设置它。


一个完整的非递归版本,考虑到不同的场景:

  • 视图控制器正在显示另一个视图
  • 视图控制器是一个UINavigationController
  • 视图控制器是一个UITabBarController

Objtovi-C

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 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;
     }
 }

斯威夫特4 +

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
    }
}


使用扩展获取用于Swift的最高视图控制器

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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()
    }
}

用途:

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

要完成Eric的回答(他在遍历时遗漏了弹出窗口、导航控制器、选项卡控制器、作为子视图添加到其他视图控制器的视图控制器),以下是我返回当前可见视图控制器的版本:

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
- (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;
    }
}

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

现在,要得到最顶层的视图控制器,您需要做的就是调用上面的方法,如下所示:

1
UIViewController *topMostViewControllerObj = [self topViewController];


这个答案包括childViewControllers,并维护了一个清晰易读的实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
+ (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;
}


我最近在我的一个项目中得到了这种情况,当网络状态改变时,它需要显示一个通知视图,无论控制器显示的是什么,无论类型是什么(uinavigationcontroller、经典控制器或自定义视图控制器)。

所以我发布了我的代码,它非常简单,实际上是基于一个协议的,所以它对于每种类型的容器控制器都是灵活的。这似乎与最后的答案有关,但方式要灵活得多。

您可以在这里获取代码:pptopmostcontroller

并使用

1
UIViewController *c = [UIViewController topMostController];

这是对Eric答案的改进:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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!


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- (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中UIApplication的简单扩展:

注:

它关心UITabBarController内的moreNavigationController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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
    }
}

简单用法:

1
2
3
if let topViewController = UIApplication.topViewController() {
    //do sth with top view controller
}


这是我的看法。感谢@stakenborg指出了跳过uialertview成为最顶级控制器的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
-(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版本:创建一个文件,将其命名为UIWindowExtension.swift并粘贴以下代码段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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
}

在任何地方使用它:

1
2
3
if let topVC = getTopViewController() {

}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
@implementation UIWindow (Extensions)

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

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

    return topController;
}

@end


这就是对我有用的。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 + (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;
 }

这个解决方案是最完整的。考虑到:ui导航控制器uipageview控制器UitababarController(UitababarController)以及从顶视图控制器中显示的最高视图控制器

示例在Swift 3中。

有3个过载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
//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解决方案:

1
2
3
4
5
6
7
8
static func topMostController() -> UIViewController {
    var topController = UIApplication.sharedApplication().keyWindow?.rootViewController
    while (topController?.presentedViewController != nil) {
        topController = topController?.presentedViewController
    }

    return topController!
}

扩展@eric的答案时,您需要注意,keywindow实际上是您想要的窗口。例如,如果在警报视图中点击某个内容后尝试使用此方法,则keywindow实际上是警报的窗口,这无疑会给您带来问题。当我通过警报处理深层链接时,这在野外发生了,并导致了没有堆栈跟踪的sigabrts。要调试的全部婊子。

下面是我现在使用的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- (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 4.2扩展

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
    }
}

从任何地方使用它,比如,

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

或者像,

1
2
3
4
 UIApplication.topViewController()?
                    .navigationController?
                    .popToViewController(yourController,
                                         animated: true)

适用于任何类,如uinavigationcontroller、uitabarcontroller

享受!


又一个快速的解决方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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)

}

快速解决方案,在AppDelegate中实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
}

我认为大多数答案都完全忽略了UINavigationViewController,所以我用下面的实现处理了这个用例。

1
2
3
4
5
6
7
8
9
10
11
12
+ (UIViewController *)topMostController {
    UIViewController * topController = [UIApplication sharedApplication].keyWindow.rootViewController;
    while (topController.presentedViewController || [topController isMemberOfClass:[UINavigationController class]]) {
        if([topController isMemberOfClass:[UINavigationController class]]) {
            topController = [topController childViewControllers].lastObject;
        } else {
            topController = topController.presentedViewController;
        }
    }

    return topController;
}

不确定这是否有助于通过查找最顶层的视图控制器来完成您正在尝试的操作,但我试图呈现一个新的视图控制器,但是如果根视图控制器已经有了模式对话框,它将被阻止,因此我将使用此代码循环到所有模式视图控制器的顶层:

1
2
3
4
5
6
7
UIViewController* parentController =[UIApplication sharedApplication].keyWindow.rootViewController;

while( parentController.presentedViewController &&
       parentController != parentController.presentedViewController )
{
    parentController = parentController.presentedViewController;
}

下面的两个函数有助于在视图控制器堆栈上找到TopViewController。稍后您可能需要自定义,但对于这段代码来说,理解TopViewController或ViewController堆栈的概念是非常棒的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
- (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的类类型。


使用下面的扩展来获取当前可见的UIViewController。为Swift 4.0及更高版本工作

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
extension UIApplication {

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

如何使用?

1
let objViewcontroller = UIApplication.topViewController()

很多答案都是不完整的。虽然这是在Objective-C中,但这是目前我可以将它们作为非递归块放在一起的最好的编译:

  • 链接到gist,以防修改:https://gist.github.com/benguild/0d149bb3caabea2dac3dca58c0816

  • 参考/比较代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
UIViewController *(^topmostViewControllerForFrontmostNormalLevelWindow)(void) = ^UIViewController *{
    // NOTE: Adapted from various stray answers here:
    //   https://stackoverflow.com/questions/6131205/iphone-how-to-find-topmost-view-controller/20515681

    UIViewController *viewController;

    for (UIWindow *window in UIApplication.sharedApplication.windows.reverseObjectEnumerator.allObjects) {
        if (window.windowLevel == UIWindowLevelNormal) {
            viewController = window.rootViewController;
            break;
        }
    }

    while (viewController != nil) {
        if ([viewController isKindOfClass:[UITabBarController class]]) {
            viewController = ((UITabBarController *)viewController).selectedViewController;
        } else if ([viewController isKindOfClass:[UINavigationController class]]) {
            viewController = ((UINavigationController *)viewController).visibleViewController;
        } else if (viewController.presentedViewController != nil && !viewController.presentedViewController.isBeingDismissed) {
            viewController = viewController.presentedViewController;
        } else if (viewController.childViewControllers.count > 0) {
            viewController = viewController.childViewControllers.lastObject;
        } else {
            BOOL repeat = NO;

            for (UIView *view in viewController.view.subviews.reverseObjectEnumerator.allObjects) {
                if ([view.nextResponder isKindOfClass:[UIViewController class]]) {
                    viewController = (UIViewController *)view.nextResponder;

                    repeat = YES;
                    break;
                }
            }

            if (!repeat) {
                break;
            }
        }
    }

    return viewController;
};

另一个快速的解决方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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
    }
}

Swift:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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;
    }
}
}

用途:

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

我的问题有点不同,我在我的应用程序中使用了swrevalview控制器。我用了钟宇晨的答案,但它总是把topviewcontroller作为swreviewcontroller返回。对于那些使用swrevalview控制器或其他pod开发侧菜单的用户。以下是我对余晨钟的回答的延伸:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
extension UIApplication {
class func topViewController() -> UIViewController? {
    var topVC = shared.keyWindow!.rootViewController
    while true {
        if let presented = topVC?.presentedViewController {
            topVC = presented
        } else if let nav = topVC as? UINavigationController {
            topVC = nav.visibleViewController
        } else if let tab = topVC as? UITabBarController {
            topVC = tab.selectedViewController
        }else if let swRVC = topVC as? SWRevealViewController {
            topVC = swRVC.frontViewController
        } else {
            break
        }
    }
    return topVC
}
}

Swift 4.2中的一个简明而全面的解决方案考虑了uinavigationcontrollers、uitabarcontrollers、presented和child view控制器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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()
  }
}

用途:

1
let viewController = UIApplication.shared.topmostViewController()

我想这里可能有一件事被忽视了。也许最好将父级viewcontroller传递到使用viewcontroller的函数中。如果您在视图层次结构中四处寻找顶部视图控制器,那么它可能违反了模型层和UI层的分离,并且是一种代码味道。只要指出这一点,我就做了同样的事情,然后意识到仅仅把它传递给函数就简单多了,方法是让模型操作返回到我引用视图控制器的UI层。


这对于从任何根视图控件中查找Top ViewController 1都非常有用。

1
2
3
4
5
6
7
8
9
10
11
+ (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];


另一种解决方案依赖于响应者链,根据第一响应者是什么,响应者链可能工作,也可能不工作:

  • 找第一个响应者。
  • 获取与第一个响应程序关联的uiviewController。
  • 伪代码示例:

    1
    2
    3
    4
    5
    + (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;
    }

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

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

    以下是uinavigationcontroller.h的摘录:

    1
    2
    @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.


    您可以使用

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


    我认为Rajesh的解决方案几乎是完美的,但我认为最好从上到下遍历子视图,我改为:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    + (UIViewController *)topViewController:(UIViewController *)viewController{

        if (viewController.presentedViewController)
        {

                UIViewController *presentedViewController = viewController.presentedViewController;
                return [self topViewController:presentedViewController];
         }
         else if ([viewController isKindOfClass:[UITabBarController class]])
         {

                UITabBarController *tabBarController = (UITabBarController *)viewController;
                return [self topViewController:tabBarController.selectedViewController];
        }

             else if ([viewController isKindOfClass:[UINavigationController class]])
        {  

                UINavigationController *navController = (UINavigationController *)viewController;

                return [self topViewController:navController.visibleViewController];
        }

        // Handling UIViewController's added as subviews to some other views.
        else {

            NSInteger subCount = [viewController.view subviews].count - 1;

            for (NSInteger index = subCount; index >=0 ; --index)
            {

                UIView *view = [[viewController.view subviews] objectAtIndex:index];

                id subViewController = [view nextResponder];    // Key property which most of us are unaware of / rarely use.

                if ( subViewController && [subViewController isKindOfClass:[UIViewController class]])
                {
                    return [self topViewController:subViewController];
                }
            }
            return viewController;
        }
    }

    以前的答案似乎不处理rootcontroller是uitabarcontroller或uinavigationcontroller的情况。

    以下是适用于这些情况的swift函数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    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
    }

    为了避免太多的复杂性,我通过在委托中创建一个viewcontroller来跟踪当前的viewcontroller,并在每个viewdidload方法中将其设置为self,这样,每当加载一个新视图时,委托中持有的viewcontroller都将对应于该视图的viewcontroller。这可能很难看,但它工作得很好,不需要有导航控制器或任何一种胡说八道的东西。


    这里是一个以uinavigationController为根的应用程序的swift实现。

    1
    2
    3
    4
    5
    6
    7
    8
      if let nav = UIApplication.sharedApplication().keyWindow?.rootViewController as? UINavigationController{
            //get the current's navigation view controller
            var vc = nav.topViewController
            while vc?.presentedViewController != nil {
                vc = vc?.presentedViewController
            }
            return vc
        }

    你应该使用:

    1
    [UIApplication sharedApplication].window.rootViewController;

    [UIApplication sharedApplication].keyWindow上有uiactionsheet时,不应该使用本答案中提到的keywindow。