ChatGPT解决这个技术问题 Extra ChatGPT

How to find topmost view controller on iOS

I've run into a couple of cases now where it would be convenient to be able to find the "topmost" view controller (the one responsible for the current view), but haven't found a way to do it.

Basically the challenge is this: Given that one is executing in a class that is not a view controller (or a view) [and does not have the address of an active view] and has not been passed the address of the topmost view controller (or, say, the address of the navigation controller), is it possible to find that view controller? (And, if so, how?)

Or, failing that, is it possible to find the topmost view?

So you're saying it's not possible.
@Daniel no, I'm saying that it seems like your code could use some re-designing, because you should rarely need to know this. Also, the idea of "topmost" is only valid in certain contexts, and even then not always.
@Daniel I had misread your question. There are lots of ifs and buts trying to answer this one. It depends on your view controller flow. @Wilbur's answer should be a good starting point to trace it down.
Well, let's simplify it to a specific case. If I wanted to write a clone of UIAlertView, how would I do it? Note that it can function fine without being passed any addressibility to other controllers or views.
@Daniel: Adding a second UIWindow works well for alert view-like overlays.

v
vrwim

I think you need a combination of the accepted answer and @fishstix's

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

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

    return topController;
}

Swift 3.0+

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
}

In addition, you can check for UINavigationController and ask for its topViewController or even check for UITabBarController and ask for selectedViewController. This will get you the view controller that is currently visible to the user.
This is an incomplete solution, since it only traverses the hierarchy of modally presented view controllers, not the hierarchy of childViewControllers (as used by UINavigationController, UITabBarController, etc.).
This is a great way to abstract out the presenting of a modal view controller that resumes to the current application state, in my case it was a password reentry screen after the application timed out. Thanks!
@algal: not really: UITabBarController, UINavigationController are already the topmost view controllers in the hierarchy. Depending on what you want to do with the "topmost controller" you might not want to traverse them at all and fiddle with their content. In my case it was to present a modal controller on top of everything, and for that I need to get the UINaviationController or UITabBarController, not their content!!
@Rick77, if this is true, your one little comment buried down here renders unnecessary the tons of complicated modifications in the other answers. Since no one else mentions this at all, I feel I have to ask you to affirm that it's true. And if it is, it is so important that it deserves to be an answer all its own. Because the large majority of the other answers do backflips trying to address this issue. You would be saving lives!
C
Community

To complete JonasG's answer (who left out tab bar controllers while traversing), here is my version of returning the currently visible view controller:

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

Nice, yeah I forgot about TabBar controllers :P
Doesn't include childViewControllers
Look at my answer below which improves above answer by handling the cases @kleo left out such as popovers, view controllers added as subviews to some other view controllers while traversing
If you are using return [self topViewControllerWithRootViewController:navigationController.visibleViewController];, visibleViewController itself returning the presented view controller(IF ANY), even if it is a UIAlertController. For someone who need to avoid ui alert controller, use topViewController instead of visibleViewController
Just to add my 50 cent to this - I was struggling to get this working in my viewcontroller that loads a webView.. the reason why I couldn't get this working was because the view was still not ready (didn't finish loading) and therefore it wasn't visible. That led to a situation where getting a topViewContoller was failing, because the UINavigationController was trying to get a visible ViewController whilst there was no visible ViewController yet. So if anyone faces this issue, make sure your view finishes loading before you make a call to the above topViewController method.
W
Wilbur Vandrsmith

iOS 4 introduced the rootViewController property on UIWindow:

[UIApplication sharedApplication].keyWindow.rootViewController;

You'll need to set it yourself after you create the view controller though.


Wilbur, this will give you the opposite of what the op asked for. rootViewController is the base view controller rather than the top most.
m4rkk: "Top-most" depends on which direction you're looking from. Do new controllers get added to the top (stack-like) or the bottom (tree-like)? In any case, the OP mentioned the navigation controller as being on top, which implies the grows-downward view.
Word „top“ is used for the view controller, that is visualy on the top (like -[UINavigationController topViewController]). Then there is word „root“, which is the root of the tree (like -[UIWindow rootViewController].
@ImpurestClub I'm not able to find in in the documentation, not does Xcode seem to find it.
@adib no, it belongs to UINavigationController
Y
Yuchen

A complete non-recursive version, taking care of different scenarios:

The view controller is presenting another view

The view controller is a UINavigationController

The view controller is a 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+

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

I named it visibleViewController to make it clear what it does.
Thank you! I was struggling all day to find a way to present a ViewController after user tapping on notification either with the app on background or in foreground!
how can i avoid uialertcontroller when alert is showing
Z
Zoltán Matók

Getting top most view controller for Swift using extensions

Code:

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

Usage:

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

excellent - thank you very much for this solution. The subviews'trick was needed ! Again, many thanks, you saved my day.
H
Hardik Thakkar

To complete Eric's answer (who left out popovers, navigation controllers, tabbarcontrollers, view controllers added as subviews to some other view controllers while traversing), here is my version of returning the currently visible view controller:

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

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

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

And now all you need to do to get top most view controller is call the above method as follows:

UIViewController *topMostViewControllerObj = [self topViewController];

Missing SplitViewController as well?
A
Awesome-o

This answer includes childViewControllers and maintains a clean and readable implementation.

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

Updated some code, due too show what controller it is by minimizing up and restoring it again. nik-kov-ios-developer.blogspot.ru/2016/12/…
Hey, come on, where is your "topVisibleViewController" ?
i
ipodishima

I recently got this situation in one my project, which required to displayed a notification view whatever the controller displayed was and whatever was the type (UINavigationController, classic controller or custom view controller), when network status changed.

So I juste released my code, which is quite easy and actually based on a protocol so that it is flexible with every type of container controller. It seems to be related with the last answers, but in a much flexible way.

You can grab the code here : PPTopMostController

And got the top most controller using

UIViewController *c = [UIViewController topMostController];

A
Ahmadreza

For latest Swift Version:
Create a file, name it UIWindowExtension.swift and paste the following snippet:

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.shared.delegate
    if let window = appDelegate!.window {
        return window?.visibleViewController
    }
    return nil
}

Use it anywhere as:

if let topVC = getTopViewController() {

}

I don't want to change your answer too much but would suggest a few things. 1. Add support for UISplitViewController. 2. use switch instead of if else. 3. not sure you need a static function as well, I think you could do this easily in the first instance level var you declared. 4. Probably best not to create too many global functions but that's a matter of taste. You can use one line of code to achieve the global function's effect: UIApplication.sharedApplication().delegate?.window?.visibleViewController
J
JonasG

This is an improvement to Eric's answer:

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) is a helper function.

Now all you need to do is call topMostController() and the top most UIViewController should be returned!


Since 1983 I would say. Remember that Objective-C contains C... Wrapping ObjC code in C functions is a common practice, so yeah, this is Objective-C code.
@JonasG Hi Jonas, In what circumstances do you prefer wrapping ObjC code in C ? Because, I sometimes see C functions like this and can not distinguish usage. Does wrapping code in C provide any performance benefits?
@OzBoz In situations where it's not immediately clear which class that self should belong to.
C
Community

Use below extension to grab current visible UIViewController. Worked for Swift 4.0 and later

Swift 4.0 and Later:

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

How to use?

let objViewcontroller = UIApplication.topViewController()

Shouldn't this test for presentedViewController first, before the UINavigationController and UITabBarController cases? Otherwise, if a view controller is modally presented from a UINavigationController or UITabBarController, it won't be returned as the top view controller, even though it's the view controller that's visible.
F
FishStix
@implementation UIWindow (Extensions)

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

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

    return topController;
}

@end

I don't think you satisfied the condition stated in the original post.
K
Kamran Khan

Here is my take on this. Thanks to @Stakenborg for pointing out the way to skip getting UIAlertView as the top most controller

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

You should avoid naming methods as getSomething: in Objective-C. This has a special meaning (more: cocoadevcentral.com/articles/000082.php) and you do not satisfy these requirements in your code.
B
Bartłomiej Semańczyk

Simple extension for UIApplication in Swift:

NOTE:

It cares about moreNavigationController within UITabBarController

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

Simple usage:

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

YES YES YES - there are many solutions around the web for finding the topMostViewController but if you're app has tab bar with a More tab, YOU MUST handle it a little different.
l
lifuqing_ios
- (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;
    }
}

I used this, but note that it breaks when there is more than one presented view controller
S
Saranjith

Swift 4.2 Extension

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

Use it from anywhere like,

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

or like,

 UIApplication.topViewController()?
                    .navigationController?
                    .popToViewController(yourController,
                                         animated: true)

Fit to any classes like UINavigationController, UITabBarController

Enjoy!


@BilalBakhrom says "Upvoted. I think your answer is the best. You cannot call directly topViewController() method. UIApplication class is singleton, use an instance called "shared"." in an edit that I've voted to reject. If this is actually correct, please click here to be taken to a menu where you can approve that edit.
'keyWindow' was deprecated in iOS 13.0: Should not be used for applications that support multiple scenes as it returns a key window across all connected scenes
n
nalexn

A concise yet comprehensive solution in Swift 4.2, takes into account UINavigationControllers, UITabBarControllers, presented and child view controllers:

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

Usage:

let viewController = UIApplication.shared.topmostViewController()

M
Martin Algesten

Yet another Swift solution

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)

}

T
Tom Andersen

Here is what worked for me.

I found that sometimes the controller was nil on the key window, as the keyWindow is some OS thing like an alert, etc.

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

S
Stakenborg

Expanding on @Eric's answer, you need to be careful that the keyWindow is actually the window you want. If you are trying to utilize this method after tapping something in an alert view for example, the keyWindow will actually be the alert's window, and that will cause problems for you no doubt. This happened to me in the wild when handling deep links via an alert and caused SIGABRTs with NO STACK TRACE. Total bitch to debug.

Here's the code I'm using now:

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

Feel free to mix this with whatever flavor of retrieving the top view controller you like from the other answers on this question.


Have you found this to be a complete solution? So many of the other answers are hugely complicated, trying to account for so many edge cases. I want this to be true, it's so simple and elegant.
I've never had a problem with it. If you're not doing anything unusual with your nav stack this should work, otherwise some of the other solutions handle more complicated cases.
E
Esqarrouth

Alternative Swift solution:

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

    return topController!
}

M
Marc Renaud

This solution is the most complete. It takes in consideration: UINavigationController UIPageViewController UITabBarController And the topmost presented view controller from the top view controller

The example is in Swift 3.

There are 3 overloads

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

E
Edward Novelo

Great solution in Swift, implement in 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
}

B
Ben Guild

A lot of these answers are incomplete. Although this is in Objective-C, this is the best compilation of all of them that I could put together for right now, as a non-recursive block:

Link to Gist, in case it gets revised: https://gist.github.com/benguild/0d149bb3caaabea2dac3d2dca58c0816

Code for reference/comparison:

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

A
Anil Arigela

I know its very late and might be redundant. But following is the snippet I came up with which is working for me :

    static func topViewController() -> UIViewController? {
        return topViewController(vc: UIApplication.shared.keyWindow?.rootViewController)
    }

    private static func topViewController(vc:UIViewController?) -> UIViewController? {
        if let rootVC = vc {
            guard let presentedVC = rootVC.presentedViewController else {
                return rootVC
            }
            if let presentedNavVC = presentedVC as? UINavigationController {
                let lastVC = presentedNavVC.viewControllers.last
                return topViewController(vc: lastVC)
            }
            return topViewController(vc: presentedVC)
        }
        return nil
    }

B
Bobj-C

Swift:

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

Usage:

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

A
Aamir

I think most of the answers have completely ignored UINavigationViewController, so I handled this use case with following implementation.

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

R
Reporter

This works great for finding the top viewController 1 from any root view controlle

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

T
Toland Hon

Not sure if this will help what you're trying to accomplish by finding the topmost view controller, but I was trying to present a new view controller, but if my root view controller already had a modal dialog, it would be blocked, so I would cycle to the top of all modal view controllers using this code:

UIViewController* parentController =[UIApplication sharedApplication].keyWindow.rootViewController;

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

T
Tapas Pal

you could find the top most view controller by using

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

Except that, if you actually read the question, self has no navigationController property.