How to find topmost view controller on iOS

228,385

Solution 1

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.

Solution 2

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
}

Solution 3

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

Solution 4

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

Solution 5

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()
Share:
228,385

Related videos on Youtube

Hot Licks
Author by

Hot Licks

Older than dirt. I got t-shirts older than you, kiddo.

Updated on June 09, 2021

Comments

  • Hot Licks
    Hot Licks almost 3 years

    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?

    • Hot Licks
      Hot Licks almost 13 years
      So you're saying it's not possible.
    • Dave DeLong
      Dave DeLong almost 13 years
      @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.
    • Deepak Danduprolu
      Deepak Danduprolu almost 13 years
      @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.
    • Hot Licks
      Hot Licks almost 13 years
      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.
    • Wilbur Vandrsmith
      Wilbur Vandrsmith almost 13 years
      @Daniel: Adding a second UIWindow works well for alert view-like overlays.
    • Hot Licks
      Hot Licks almost 13 years
      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.
    • Bogdan
      Bogdan over 11 years
      @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.
  • NICE8xxx
    NICE8xxx over 12 years
    Wilbur, this will give you the opposite of what the op asked for. rootViewController is the base view controller rather than the top most.
  • Wilbur Vandrsmith
    Wilbur Vandrsmith over 12 years
    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.
  • Hot Licks
    Hot Licks almost 12 years
    I don't think you satisfied the condition stated in the original post.
  • Tricertops
    Tricertops almost 11 years
    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
    Tricertops almost 11 years
    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
    algal almost 11 years
    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.).
  • JonasG
    JonasG over 10 years
    Nice, yeah I forgot about TabBar controllers :P
  • Eric
    Eric over 10 years
    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!
  • atastrophic
    atastrophic over 10 years
    for a root controller of navigation controller that was presented on existing view controller, top controller won't be the window's root controller..
  • Hot Licks
    Hot Licks about 10 years
    Except that, if you actually read the question, self has no navigationController property.
  • Rick77
    Rick77 about 10 years
    @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
    Vassily about 10 years
    nice One, rootViewController is always behind modals so invisible while showing modal.
  • Awesome-o
    Awesome-o almost 10 years
    Doesn't include childViewControllers
  • Gingi
    Gingi almost 10 years
    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.
  • JonasG
    JonasG over 9 years
    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.
  • combinatorial
    combinatorial over 9 years
    This would be better as an answer if it explained why it is better than the many other answers to this question..
  • Infinite Recursion
    Infinite Recursion over 9 years
    I hope my edit tells you how to analyse constructively.
  • Rajesh
    Rajesh over 9 years
    Look at my answer 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
    Rajesh over 9 years
    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
  • Rajesh
    Rajesh over 9 years
    Look at my answer below which improves above answer by handling the cases @kleo left out such as popovers, view controllers added as subviews to some other view controllers while traversing
  • Drux
    Drux over 9 years
    @ImpurestClub visibleViewController, on an iPhone?
  • ImpurestClub
    ImpurestClub over 9 years
    @Drux Yup! Used it quite a few times without any issues.
  • Drux
    Drux over 9 years
    @ImpurestClub I'm not able to find in in the documentation, not does Xcode seem to find it.
  • OzBoz
    OzBoz over 9 years
    @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?
  • Hot Licks
    Hot Licks about 9 years
    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
    J_Tuck about 9 years
    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
  • adib
    adib about 9 years
    @ImpurestClub visibleViewController belongs to UIViewController and not UIWindow.
  • adib
    adib about 9 years
    @OzBoz In situations where it's not immediately clear which class that self should belong to.
  • David H
    David H almost 9 years
    @adib no, it belongs to UINavigationController
  • onmyway133
    onmyway133 almost 9 years
    @Eric this is not always correct, the rootViewController may something not be the presentingViewController, because some view controllers can set ` definesPresentationContext` to YES
  • Johnykutty
    Johnykutty over 8 years
    If you are using return [self topViewControllerWithRootViewController:navigationController‌​.visibleViewControll‌​er];, 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
  • Le Mot Juiced
    Le Mot Juiced over 8 years
    @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
    Le Mot Juiced over 8 years
    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.
  • Rick77
    Rick77 over 8 years
    @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.
  • Stakenborg
    Stakenborg over 8 years
    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.
  • Nat
    Nat over 8 years
    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.
  • SPQR3
    SPQR3 about 8 years
    @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.
  • mbuster
    mbuster about 8 years
    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.
  • Eugene P
    Eugene P almost 8 years
    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.
  • HotJard
    HotJard almost 8 years
    for child view controllers, need just add one else if (rootViewController.childViewControllers.count > 0) { return [self topViewControllerWithRootViewController:rootViewController.c‌​hildViewControllers.‌​lastObject]; }
  • Jordan Smith
    Jordan Smith over 7 years
    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
  • Nike Kov
    Nike Kov over 7 years
    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/…
  • apinho
    apinho almost 7 years
    Missing SplitViewController as well?
  • Paradise
    Paradise over 6 years
    Hey, come on, where is your "topVisibleViewController" ?
  • David Crow
    David Crow about 6 years
    Using this will potentially result in the view being displayed in a UIAlertViewController - test against this if you plan on using this incomplete solution.
  • Jonny
    Jonny about 6 years
    I named it visibleViewController to make it clear what it does.
  • Chuck Boris
    Chuck Boris almost 6 years
    I used this, but note that it breaks when there is more than one presented view controller
  • Steve Gear
    Steve Gear almost 6 years
    If [rootViewController isKindOfClass:[UIAlertController class] then how to get top most view controller under UIAlertController
  • Andy Obusek
    Andy Obusek over 5 years
    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.
  • Henry Heleine
    Henry Heleine over 5 years
    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!
  • wizzwizz4
    wizzwizz4 about 5 years
    @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.
  • Drew
    Drew over 4 years
    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.
  • iKK
    iKK about 4 years
    excellent - thank you very much for this solution. The subviews'trick was needed ! Again, many thanks, you saved my day.
  • Shardon
    Shardon almost 3 years
    Thank you! I was struggling all day to find a way to present a ViewController after user tapping on notification either with the app on background or in foreground!
  • famfamfam
    famfamfam over 2 years
    how can i avoid uialertcontroller when alert is showing
  • Carmen
    Carmen about 2 years
    'keyWindow' was deprecated in iOS 13.0: Should not be used for applications that support multiple scenes as it returns a key window across all connected scenes