By Hot Licks


2011-05-25 21:31:17 8 Comments

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?

30 comments

@Anil Arigela 2020-01-09 07:48:08

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
    }

@IMHiteshSurani 2019-05-01 07:19:28

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

@Drew 2020-02-01 20:38:50

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.

@Saranjith 2019-02-08 05:18:26

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!

@wizzwizz4 2019-04-27 09:22:47

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

@Ben Guild 2017-10-22 14:54:57

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

@Gulfam Khan 2019-01-28 15:43:35

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

@Eric 2012-10-02 04:51:54

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
}

@Tricertops 2013-06-01 17:07:19

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.

@algal 2013-06-04 12:45:08

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

@erversteeg 2013-11-07 18:09:32

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!

@Rick77 2014-03-07 11:16:54

@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!!

@Vassily 2014-03-20 13:30:15

nice One, rootViewController is always behind modals so invisible while showing modal.

@Gingi 2014-06-20 20:29:53

The topmost view controller for UIApplications keyWindow.rootViewController` is always _UIModalItemsPresentingViewController for me (iOS 7). However, when I use, the appDelegate.window property, it returns the controller I wanted.

@Rajesh 2014-09-26 13:57:13

Look at my answer 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

@onmyway133 2015-07-29 04:28:32

@Eric this is not always correct, the rootViewController may something not be the presentingViewController, because some view controllers can set ` definesPresentationContext` to YES

@Le Mot Juiced 2015-11-25 12:46:52

@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!

@Rick77 2015-11-25 13:12:17

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

@SPQR3 2016-03-09 17:02:08

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

@Eugene Prokoshev 2016-06-20 12:47:13

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.

@crow 2018-03-30 22:53:57

Using this will potentially result in the view being displayed in a UIAlertViewController - test against this if you plan on using this incomplete solution.

@Henry Heleine 2018-09-27 22:43:28

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!

@nalexn 2018-12-19 15:31:31

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

@possen 2018-10-21 18:48:35

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.

@Rajesh 2013-12-11 09:45:21

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

@apinho 2017-06-16 11:06:55

Missing SplitViewController as well?

@Yuchen Zhong 2016-09-09 16:34:23

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

@Jonny 2018-04-11 05:34:10

I named it visibleViewController to make it clear what it does.

@Aamir 2017-11-27 21:25:49

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

@Varuna 2014-11-11 07:23:43

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

@Marc Renaud 2016-12-21 20:15:42

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
}

@bzz 2016-06-30 13:38:41

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

@Bartłomiej Semańczyk 2015-02-24 14:54:41

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
}

@Andy Obusek 2018-09-10 14:50:14

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.

@Martin Algesten 2016-06-04 10:37:10

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)

}

@Janusz Chudzynski 2016-05-30 14:09:25

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
    }

@Ashok 2016-03-03 13:40:05

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

}

@Jordan Smith 2016-08-30 04:29:51

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?.visibleV‌​iewController

@iDevAmit 2015-10-16 04:52:02

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.

@Awesome-o 2014-06-17 20:25:58

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

@Nik Kov 2016-12-27 08:49:18

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

@Paradise 2017-12-14 08:41:13

Hey, come on, where is your "topVisibleViewController" ?

@Edward Novelo 2015-07-03 22:45:51

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
}

@Siguang Zhang 2015-05-17 03:59:15

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

@Dragouf 2015-04-23 08:16:05

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
}

@J_Tuck 2015-03-31 04:59:22

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.

@Hot Licks 2015-03-31 11:44:46

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]

@J_Tuck 2015-03-31 17:07:56

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

@Esqarrouth 2015-03-08 22:00:29

Alternative Swift solution:

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

    return topController!
}

@Bobj-C 2014-12-19 15:07:57

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

@Senseful 2014-12-07 01:52:36

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

@Kamran Khan 2014-10-31 07:38:27

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

@Vive 2015-12-15 15:20:34

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.

@Stakenborg 2014-09-26 16:17:52

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.

@Le Mot Juiced 2015-11-25 12:55:09

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.

@Stakenborg 2015-11-29 21:37:59

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.

@u2takey 2014-09-11 03:48:02

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.

@combinatorial 2014-09-11 04:37:49

This would be better as an answer if it explained why it is better than the many other answers to this question..

@Infinite Recursion 2014-09-11 06:09:22

I hope my edit tells you how to analyse constructively.

Related Questions

Sponsored Content

37 Answered Questions

[SOLVED] How to check iOS version?

  • 2010-07-26 23:30:11
  • John
  • 450528 View
  • 843 Score
  • 37 Answer
  • Tags:   ios objective-c

43 Answered Questions

[SOLVED] Passing Data between View Controllers

94 Answered Questions

16 Answered Questions

[SOLVED] How to tell if UIViewController's view is visible

23 Answered Questions

[SOLVED] How do I check if a string contains another string in Objective-C?

11 Answered Questions

[SOLVED] How do I convert an NSString value to NSData?

25 Answered Questions

[SOLVED] How do I sort an NSMutableArray with custom objects in it?

2 Answered Questions

How to switch view controllers from UIApplication

1 Answered Questions

Sponsored Content