271

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?

Cœur
  • 32,421
  • 21
  • 173
  • 232
Hot Licks
  • 44,830
  • 15
  • 88
  • 146
  • So you're saying it's not possible. – Hot Licks May 26 '11 at 00:06
  • @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. – Dave DeLong May 26 '11 at 00:08
  • @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. – Deepak Danduprolu May 26 '11 at 00:41
  • 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. – Hot Licks May 26 '11 at 02:17
  • 4
    @Daniel: Adding a second UIWindow works well for alert view-like overlays. – Wilbur Vandrsmith May 26 '11 at 23:09
  • Yeah, UIWindow appears to be the way to do an alert. Doesn't solve the other problem of figuring out the topmost view controller (where one wants to place another on top), but I guess those need to be dealt with separately. – Hot Licks Jun 04 '11 at 01:07
  • @DaveDeLong What if you really need in some particular case more then an UIAlertView in a module where you only process data? You don't want to mess around with a controller reference in the data module, taking care to set it properly in all UI where you call the data module. Or you do? And maybe better is to place an in-between layer... I'm just thinking here... you might be right though. – Bogdan Dec 07 '12 at 11:09

41 Answers41

443

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
}
vrwim
  • 9,459
  • 11
  • 57
  • 107
Eric
  • 7,438
  • 4
  • 17
  • 31
  • 4
    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. – Tricertops Jun 01 '13 at 17:07
  • 35
    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.). – algal Jun 04 '13 at 12:45
  • 3
    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! – erversteeg Nov 07 '13 at 18:09
  • 15
    @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 Mar 07 '14 at 11:16
  • nice One, rootViewController is always behind modals so invisible while showing modal. – Vassily Mar 20 '14 at 13:30
  • The topmost view controller for UIApplication`s `keyWindow.rootViewController` is always `_UIModalItemsPresentingViewController` for me (iOS 7). However, when I use, the `appDelegate.window` property, it returns the controller I wanted. – Gingi Jun 20 '14 at 20:29
  • Look at my [answer](http://stackoverflow.com/a/20515681/1920799) below which improves above answer by handling the cases @Eric left out such as popovers, navigation controllers, tabbarcontrollers, modal views, view controllers added as subviews to some other view controllers while traversing – Rajesh Sep 26 '14 at 13:57
  • @Eric this is not always correct, the rootViewController may something not be the `presentingViewController`, because some view controllers can set ` definesPresentationContext` to YES – onmyway133 Jul 29 '15 at 04:28
  • 1
    @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! – Le Mot Juiced Nov 25 '15 at 12:46
  • 1
    @LeMotJuiced unless something changed since I wrote that (I'm not writing iOs code in a while), my observation is true: UINavigationController and UITabControllers are "containers" , and they are logically (and in the hierarchy) "on top". They have a hierarchy of their own, but it's another thing. – Rick77 Nov 25 '15 at 13:12
  • 1
    @Rick77, if one's goal is to present a ViewController on top of everything this is a perfect and simple solution, and all the other answers here are overcomplicated and less future proof. You don't have to care about what the rootVC is and its inner workings, it's present in the View hierarchy, so one can always present a modal on it. – SPQR3 Mar 09 '16 at 17:02
  • The implementation is a bit naive. - Does not include check for **UIAlertControllers**. -Does not include check for dismissing controller, that might be still in the view hierarchy. – Eugene Prokoshev Jun 20 '16 at 12:47
  • Using this will potentially result in the view being displayed in a UIAlertViewController - test against this if you plan on using this incomplete solution. – crow Mar 30 '18 at 22:53
  • I spent hours trying to work out which of my many view controllers was on top of the window across my iPad app and the presentedViewController API worked it out immediately. Thank you Eric! – Henry Heleine Sep 27 '18 at 22:43
151

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;
    }
}
Community
  • 1
  • 1
kleo
  • 1,556
  • 1
  • 10
  • 8
  • 2
    Nice, yeah I forgot about TabBar controllers :P – JonasG Aug 25 '13 at 14:24
  • 9
    Doesn't include `childViewControllers` – Awesome-o Jun 17 '14 at 20:07
  • Look at my [answer](http://stackoverflow.com/a/20515681/1920799) 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 – Rajesh Sep 26 '14 at 13:58
  • 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 – Johnykutty Sep 14 '15 at 07:30
  • 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. – mbuster May 17 '16 at 12:14
  • for child view controllers, need just add one else if (rootViewController.childViewControllers.count > 0) { return [self topViewControllerWithRootViewController:rootViewController.childViewControllers.lastObject]; } – HotJard Jul 06 '16 at 15:23
  • If [rootViewController isKindOfClass:[UIAlertController class] then how to get top most view controller under UIAlertController – Steve Gear Jul 03 '18 at 12:53
81

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 Vandrsmith
  • 4,988
  • 29
  • 32
  • 165
    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 Jan 25 '12 at 21:17
  • 3
    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. – Wilbur Vandrsmith Jan 28 '12 at 00:42
  • 51
    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]`. – Tricertops Jun 01 '13 at 17:04
  • for a root controller of navigation controller that was presented on existing view controller, top controller won't be the window's root controller.. – tGilani Dec 10 '13 at 07:00
  • Look at my [answer](http://stackoverflow.com/a/20515681/1920799) below which improves above answer by handling the cases @Wilbur left out such as popovers, navigation controllers, tabbarcontrollers, modal views, view controllers added as subviews to some other view controllers while traversing – Rajesh Sep 26 '14 at 13:56
  • @ImpurestClub `visibleViewController`, on an iPhone? – Drux Jan 18 '15 at 16:27
  • @Drux Yup! Used it quite a few times without any issues. – ImpurestClub Jan 18 '15 at 20:47
  • 13
    @ImpurestClub I'm not able to find in in the [documentation](https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UIWindow_Class/index.html), not does Xcode seem to find it. – Drux Jan 18 '15 at 20:49
  • @ImpurestClub `visibleViewController` [belongs to `UIViewController`](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UINavigationController_Class/) and not `UIWindow`. – adib May 16 '15 at 06:33
  • 5
    @adib no, it belongs to **UINavigationController** – David H May 22 '15 at 15:17
60

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
    }
}
Yuchen
  • 24,092
  • 18
  • 133
  • 193
31

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()
Zoltán Matók
  • 3,829
  • 2
  • 28
  • 55
Varuna
  • 1,148
  • 1
  • 17
  • 19
  • excellent - thank you very much for this solution. The subviews'trick was needed ! Again, many thanks, you saved my day. – iKK Feb 20 '20 at 16:35
27

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];
Hardik Thakkar
  • 13,424
  • 2
  • 80
  • 72
Rajesh
  • 820
  • 8
  • 18
21

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;
}
Awesome-o
  • 1,842
  • 1
  • 23
  • 35
  • Updated some code, due too show what controller it is by minimizing up and restoring it again. http://nik-kov-ios-developer.blogspot.ru/2016/12/find-out-what-controller-is-on-top.html – Nik Kov Dec 27 '16 at 08:49
  • Hey, come on, where is your "topVisibleViewController" ? – Paradise Dec 14 '17 at 08:41
12

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];
ipodishima
  • 274
  • 2
  • 8
10

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!

JonasG
  • 9,035
  • 12
  • 53
  • 88
  • 7
    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 Sep 03 '14 at 16:22
  • @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 Jan 23 '15 at 07:45
  • 1
    @OzBoz In situations where it's not immediately clear which class that `self` should belong to. – adib May 16 '15 at 06:36
10

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()
Community
  • 1
  • 1
Hitesh Surani
  • 9,976
  • 5
  • 37
  • 55
  • 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. – Drew Feb 01 '20 at 20:38
9

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

Use it anywhere as:

if let topVC = getTopViewController() {

}
Ashok
  • 5,085
  • 5
  • 45
  • 74
  • 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` – Jordan Smith Aug 30 '16 at 04:29
8
@implementation UIWindow (Extensions)

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

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

    return topController;
}

@end
FishStix
  • 4,560
  • 8
  • 34
  • 50
8

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;
}
Kamran Khan
  • 1,347
  • 1
  • 15
  • 18
  • You should avoid naming methods as `getSomething:` in Objective-C. This has a special meaning (more: http://cocoadevcentral.com/articles/000082.php) and you do not satisfy these requirements in your code. – Nat Dec 15 '15 at 15:20
8

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
}
Bartłomiej Semańczyk
  • 52,820
  • 43
  • 206
  • 318
  • 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. – Andy Obusek Sep 10 '18 at 14:50
7
- (UIViewController*)topViewController {
    return [self topViewControllerWithRootViewController:[UIApplication sharedApplication].keyWindow.rootViewController];
}

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

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()
nalexn
  • 9,528
  • 6
  • 39
  • 45
4

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)

}
Martin Algesten
  • 11,444
  • 2
  • 46
  • 74
4

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!

Saranjith
  • 9,547
  • 2
  • 55
  • 102
  • 1
    @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](https://stackoverflow.com/review/suggested-edits/22864792) 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.](https://stackoverflow.com/review/suggested-edits/22864792) – wizzwizz4 Apr 27 '19 at 09:22
3

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;
 }
Tom Andersen
  • 6,788
  • 2
  • 34
  • 53
3

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.

Stakenborg
  • 2,810
  • 2
  • 21
  • 29
  • 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. – Le Mot Juiced Nov 25 '15 at 12:55
  • 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. – Stakenborg Nov 29 '15 at 21:37
3

Alternative Swift solution:

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

    return topController!
}
Esqarrouth
  • 35,175
  • 17
  • 147
  • 154
3

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

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
}
Edward Novelo
  • 71
  • 1
  • 3
2

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
    }
Anil Arigela
  • 396
  • 1
  • 8
1

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:

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;
};
Ben Guild
  • 4,205
  • 5
  • 27
  • 49
1

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;
}
Aamir
  • 13,234
  • 9
  • 51
  • 63
0

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]; 
Reporter
  • 3,547
  • 5
  • 28
  • 45
johnnyg17
  • 610
  • 6
  • 14
0

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;
}
Toland Hon
  • 4,179
  • 2
  • 28
  • 34
0

you could find the top most view controller by using

NSArray *arrViewControllers=[[self navigationController] viewControllers];
UIViewController *topMostViewController=(UIViewController *)[arrViewControllers objectAtIndex:[arrViewControllers count]-1];
Tapas Pal
  • 6,566
  • 6
  • 32
  • 76
0

Another solution relies on the responder chain, which may or may not work depending on what the first responder is:

  1. Get the first responder.
  2. Get the UIViewController associated with that first responder.

Example pseudo code:

+ (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;
}
Community
  • 1
  • 1
Senseful
  • 73,679
  • 56
  • 267
  • 405
0

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)
        }
Bobj-C
  • 5,487
  • 8
  • 44
  • 75
0

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.

J_Tuck
  • 91
  • 1
  • 5
  • Quote: *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]* – Hot Licks Mar 31 '15 at 11:44
  • Storing it in the delegate and creating a variable in each class _"var appDelegate = UIApplication.sharedApplication().delegate as AppDelegate"_ gives access to a field in the appDelegate that holds the current viewController, which is set in in viewControllers viewDidLoad using a variable appDelegate defined the same way as mentioned before – J_Tuck Mar 31 '15 at 17:07
0

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
}
Dragouf
  • 4,500
  • 4
  • 41
  • 51
0

I think the solution from Rajesh is nearly perfect, but I think it is better traverse subviews from top to bottom, I changed to the following:

+ (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;
    }
}
Ravi Gautam
  • 951
  • 1
  • 9
  • 20
0

Below two function can help to find the topViewController on Stack of view controllers. You may need customization later, but for this code is awesome to understand the concept of topViewController or stack of viewControllers.

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

You can use [viewController Class] method to find out the type of class of a viewController.

iDevAmit
  • 1,312
  • 1
  • 19
  • 31
0

Here is a Swift's implementation of an app with UINavigationController's as a root.

  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
    }
Janusz Chudzynski
  • 2,620
  • 3
  • 30
  • 45
0

And another Swift solution

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
    }
}
bzz
  • 5,368
  • 22
  • 25
0

I am thinking that perhaps one thing is being overlooked here. Perhaps it is better to pass the parent viewController into the function that is using the viewController. If you are fishing around in the view hierarchy to find the top view controller that it is probably violating separation of the Model layer and UI layer and is a code smell. Just pointing this out, I did the same, then realized it was much simpler just to pass it in to function, by having the model operation return to the UI layer where I have a reference to the view controller.

possen
  • 6,778
  • 2
  • 34
  • 43
0

My issue was a bit different I an using SWRevealViewController in my application. I used Yuchen Zhong's answer but it always return topViewController as SWRevealViewController. For those who are using SWRevealViewController or some other pod to develop sideMenu. Here is my extension to Yuchen Zhong's answer:

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
}
}
Gulfam Khan
  • 750
  • 6
  • 19
-1

If the root controller is a navigation controller, correct way to find top visible controller is:

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

Here's an excerpt from 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.
amit
  • 1
  • 2
-2

You should use:

[UIApplication sharedApplication].window.rootViewController;

When there is a uiactionsheet on [UIApplication sharedApplication].keyWindow, it is not right to use keyWindow as mentioned in this answer.

Community
  • 1
  • 1
u2takey
  • 15
  • 3