Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Find the current top view controller for your iOS application
- (UIViewController *)topViewController{
return [self topViewController:[UIApplication sharedApplication].keyWindow.rootViewController];
}
- (UIViewController *)topViewController:(UIViewController *)rootViewController
{
if (rootViewController.presentedViewController == nil) {
return rootViewController;
}
if ([rootViewController.presentedViewController isMemberOfClass:[UINavigationController class]]) {
UINavigationController *navigationController = (UINavigationController *)rootViewController.presentedViewController;
UIViewController *lastViewController = [[navigationController viewControllers] lastObject];
return [self topViewController:lastViewController];
}
UIViewController *presentedViewController = (UIViewController *)rootViewController.presentedViewController;
return [self topViewController:presentedViewController];
}
@hyperboloid
Copy link

hyperboloid commented Jan 12, 2014

Quite useful. However, I would replace isMemberOfClass with isKindOfClass in case the presentedViewController is a subclass of UINavigationController.

@nicolasembleton
Copy link

nicolasembleton commented Jun 5, 2014

Nice gist.

@ibrahimkteish
Copy link

ibrahimkteish commented Dec 19, 2014

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

@yonat
Copy link

yonat commented Feb 12, 2015

Another Swift version:

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

@vrunoa
Copy link

vrunoa commented May 5, 2015

👍

@PoissonBallon
Copy link

PoissonBallon commented Sep 18, 2015

Same of yonat but in Obj-C ;)

- (UIViewController *)topViewController{
  return [self topViewController:[UIApplication sharedApplication].keyWindow.rootViewController];
}

- (UIViewController *)topViewController:(UIViewController *)rootViewController
{
  if ([rootViewController isKindOfClass:[UINavigationController class]]) {
    UINavigationController *navigationController = (UINavigationController *)rootViewController;
    return [self topViewController:[navigationController.viewControllers lastObject]];
  }
  if ([rootViewController isKindOfClass:[UITabBarController class]]) {
    UITabBarController *tabController = (UITabBarController *)rootViewController;
    return [self topViewController:tabController.selectedViewController];
  }
  if (rootViewController.presentedViewController) {
    return [self topViewController:rootViewController];
  }
  return rootViewController;
}

@AzeemKhan123
Copy link

AzeemKhan123 commented Oct 19, 2015

Awesome :)

@lxmfly123
Copy link

lxmfly123 commented Oct 28, 2015

nice one.

@Bodacious
Copy link

Bodacious commented Dec 5, 2015

Transcribed to Rubymotion

def topViewController
  topViewController(UIApplication.sharedApplication.keyWindow.rootViewController)
end

def topViewController(rootViewController)
  return rootViewController unless rootViewController.presentedViewController

  if rootViewController.presentedViewController.is_a?(UINavigationController)
    return rootViewController.presentedViewController.viewControllers.last
  end

  return topViewController(rootViewController.presentedViewController)
end

@hechen
Copy link

hechen commented Mar 2, 2016

I am so curious about the differences between this and the property visibleViewController of UINavigationViewController. Thanks~

@danionescu
Copy link

danionescu commented Mar 31, 2016

@PoissonBallon I think it should be

if (rootViewController.presentedViewController) {
    // instead of just [self topViewController:rootViewController];
    return [self topViewController:rootViewController.presentedViewController)];
}

@RGondek
Copy link

RGondek commented Apr 18, 2016

Awesome solution, nice use of recursivity ;)

@hackjie
Copy link

hackjie commented May 5, 2016

good, but how to find the current view‘s navigation controller, for example , uitableviewcell has uiimageview for photo, but it can be tapped to push another viewcontroller, like twitter tap photo, how to solve that

@fvernon
Copy link

fvernon commented May 20, 2016

A slightly more Swifty version of the variations above.

`
extension UIViewController {
class func topMostViewController() -> UIViewController? {
return UIViewController.topViewControllerForRoot(UIApplication.sharedApplication().keyWindow?.rootViewController)
}

class func topViewControllerForRoot(rootViewController:UIViewController?) -> UIViewController? {
    guard let rootViewController = rootViewController else {
        return nil
    }

    guard let presented = rootViewController.presentedViewController else {
        return rootViewController
    }

    switch presented {
    case is UINavigationController:
        let navigationController:UINavigationController = presented as! UINavigationController
        return UIViewController.topViewControllerForRoot(navigationController.viewControllers.last)

    case is UITabBarController:
        let tabBarController:UITabBarController = presented as! UITabBarController
        return UIViewController.topViewControllerForRoot(tabBarController.selectedViewController)

    default:
        return UIViewController.topViewControllerForRoot(presented)
    }
}

}

`

@dabinghao123
Copy link

dabinghao123 commented Aug 11, 2016

very nice

@DisappearPing
Copy link

DisappearPing commented Sep 2, 2016

my swift version :

extension AppDelegate {
    func findCurrentViewController() -> UIViewController{
        let rootVC = UIApplication.sharedApplication().keyWindow?.rootViewController

        return findCurrentViewController(byTempTopVC: rootVC!)
    }

    func findCurrentViewController(byTempTopVC vc: UIViewController) -> UIViewController {
        let presentedVC = vc.presentedViewController

        guard presentedVC != nil else {
            return vc
        }

        if presentedVC!.isKindOfClass(UINavigationController) {
            let theNav =  presentedVC
            let theTopVC = theNav!.childViewControllers.last
            return findCurrentViewController(byTempTopVC: theTopVC!)
        }

        return findCurrentViewController(byTempTopVC: presentedVC!)
    }
}

@nuudles
Copy link

nuudles commented Oct 5, 2016

I cleaned up @fvernon's swiftier version to be a bit more swifty:

extension UIViewController {
    class func topViewController(rootViewController rootViewController: UIViewController?) -> UIViewController? {
        guard let rootViewController = rootViewController else {
            return nil
        }

        guard let presented = rootViewController.presentedViewController else {
            return rootViewController
        }

        switch presented {
        case let navigationController as UINavigationController:
            return topViewController(rootViewController: navigationController.viewControllers.last)

        case let tabBarController as UITabBarController:
            return topViewController(rootViewController: tabBarController.selectedViewController)

        default:
            return topViewController(rootViewController: presented)
        }
    }
}

@PichuChen
Copy link

PichuChen commented Nov 3, 2016

I cleaned up @nuudles's version to be more swifty:

This version use iterator instead of recursive

extension UIApplication{
    var topViewController: UIViewController?{
        if keyWindow?.rootViewController == nil{
            return keyWindow?.rootViewController
        }
        
        var pointedViewController = keyWindow?.rootViewController
        
        while  pointedViewController?.presentedViewController != nil {
            switch pointedViewController?.presentedViewController {
            case let navagationController as UINavigationController:
                pointedViewController = navagationController.viewControllers.last
            case let tabBarController as UITabBarController:
                pointedViewController = tabBarController.selectedViewController
            default:
                pointedViewController = pointedViewController?.presentedViewController
            }
        }
        return pointedViewController
        
    }
}

and the usage example:

func applicationDidBecomeActive(_ application: UIApplication) {
    debugPrint(application.topViewController)
}

@MrDEV0
Copy link

MrDEV0 commented Jan 11, 2017

Nice and useful code snippet.

@Amr1977
Copy link

Amr1977 commented Jan 25, 2017

Tested and working @nuudles post, repeating for confirmation:

    func topViewController(rootViewController rootViewController: UIViewController?) -> UIViewController? {
        guard let rootViewController = rootViewController else {
            return nil
        }

        guard let presented = rootViewController.presentedViewController else {
            return rootViewController
        }

        switch presented {
        case let navigationController as UINavigationController:
            return topViewController(rootViewController: navigationController.viewControllers.last)

        case let tabBarController as UITabBarController:
            return topViewController(rootViewController: tabBarController.selectedViewController)

        default:
            return topViewController(rootViewController: presented)
        }
    }

@wubingwell
Copy link

wubingwell commented Apr 12, 2017

What if rootViewController is a kind of UINavigationController and has no presentedViewController

@Hawkdiver
Copy link

Hawkdiver commented Apr 25, 2017

@yonat
I tried using your code but the view i tried to load a blank page and then after like 1 min only the buttons in the view load , could you help me understand this? As to why this happens? Because I encounter the same problem when I tried testing the finger print sensor functions provided by Apple.
Thank you.

@ins0u
Copy link

ins0u commented May 17, 2017

@wubingwell : i had the same problem, solved like this

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

            default:
                if let presentedViewController = vc.presentedViewController {
                    //print(presentedViewController)
                    if let presentedViewController2 = presentedViewController.presentedViewController {
                        return UIWindow.getVisibleViewControllerFrom(vc: presentedViewController2)
                    }
                    else{
                        return vc;
                    }
                }
                else{
                    return vc;
                }
            break;
        }
        
    }

}

Usage :

if let topController = window?.visibleViewController() {
                        
     switch(topController){
         case is MyCollectionViewController:
                 print("OK CollectionViewController")
          break;
                            
         case is MyViewController:
                 print("OK ViewController")
         break;
                            
         default:
                 print("OK but default")
         break;
    }
}

@mohammadshalhoob
Copy link

mohammadshalhoob commented Aug 2, 2017

My ver to find topViewController with SSASideMenu

public extension UIApplication {

public class func topViewController(_ base: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
    if let SSASide = base as? SSASideMenu {
        if let nav = SSASide.contentViewController as? UINavigationController{
        return topViewController(nav.visibleViewController)
        }
    }
    if let nav = base as? UINavigationController {
        return topViewController(nav.visibleViewController)
    }
    
    if let tab = base as? UITabBarController {
        let moreNavigationController = tab.moreNavigationController
        
        if let top = moreNavigationController.topViewController, top.view.window != nil {
            return topViewController(top)
        } else if let selected = tab.selectedViewController {
            return topViewController(selected)
        }
    }
    
    if let presented = base?.presentedViewController {
        return topViewController(presented)
    }
    
    return base
}

@kwallace-abvio
Copy link

kwallace-abvio commented Feb 28, 2018

I like mohammadshalhoob's version because it takes moreNavigationController into account. However, I found that moreNavigationController.topViewController.view.window != nil didn't work as intended when used in viewWillAppear, before the view is attached to the window. To test whether the sixth or more view controller is selected, I just used tab.selectedIndex > 4.

@18930369486
Copy link

18930369486 commented Jan 6, 2020

I'm curious where UIViewController's children has gone?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment