How to find topmost view controller on iOS
Asked Answered
F

42

285

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?

Flexuosity answered 25/5, 2011 at 21:31 Comment(7)
So you're saying it's not possible.Flexuosity
@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.Ryder
@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.Hued
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.Flexuosity
@Daniel: Adding a second UIWindow works well for alert view-like overlays.Maryrosemarys
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.Flexuosity
@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.Strangle
M
79

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.

Maryrosemarys answered 25/5, 2011 at 21:42 Comment(5)
Wilbur, this will give you the opposite of what the op asked for. rootViewController is the base view controller rather than the top most.Karalynn
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.Maryrosemarys
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].Tymes
for a root controller of navigation controller that was presented on existing view controller, top controller won't be the window's root controller..Hazardous
it is an incorrect solution. Why is it accepted?! rootViewController is a "bottom" view controller, not "topmost"Paschall
J
457

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
}
Joselow answered 2/10, 2012 at 4:51 Comment(9)
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.Tymes
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.).Loanloanda
@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!!Kelsy
nice One, rootViewController is always behind modals so invisible while showing modal.Nestle
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.Floozy
@Joselow this is not always correct, the rootViewController may something not be the presentingViewController, because some view controllers can set ` definesPresentationContext` to YESSelfsatisfied
@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.Kelsy
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.Prudie
Using this will potentially result in the view being displayed in a UIAlertViewController - test against this if you plan on using this incomplete solution.Rugged
K
153

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;
    }
}
Kiushu answered 10/7, 2013 at 18:31 Comment(4)
Doesn't include childViewControllersIgnorant
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 visibleViewControllerAustralian
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.Pearlypearman
for child view controllers, need just add one else if (rootViewController.childViewControllers.count > 0) { return [self topViewControllerWithRootViewController:rootViewController.childViewControllers.lastObject]; }Bomar
M
79

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.

Maryrosemarys answered 25/5, 2011 at 21:42 Comment(5)
Wilbur, this will give you the opposite of what the op asked for. rootViewController is the base view controller rather than the top most.Karalynn
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.Maryrosemarys
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].Tymes
for a root controller of navigation controller that was presented on existing view controller, top controller won't be the window's root controller..Hazardous
it is an incorrect solution. Why is it accepted?! rootViewController is a "bottom" view controller, not "topmost"Paschall
L
67

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
    }
}
Lakeesha answered 9/9, 2016 at 16:34 Comment(0)
I
32

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()
Instable answered 11/11, 2014 at 7:23 Comment(0)
J
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];
Jentoft answered 11/12, 2013 at 9:45 Comment(1)
Missing SplitViewController as well?Tuyettv
I
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;
}
Ignorant answered 17/6, 2014 at 20:25 Comment(1)
Hey, come on, where is your "topVisibleViewController" ?Catalonia
R
11

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];
Ragout answered 1/6, 2013 at 15:16 Comment(0)
R
11

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

import UIKit

public extension UIWindow {
    public var visibleViewController: UIViewController? {
        return UIWindow.getVisibleViewControllerFrom(self.rootViewController)
    }

    public static func getVisibleViewControllerFrom(_ vc: UIViewController?) -> UIViewController? {
        if let nc = vc as? UINavigationController {
            return UIWindow.getVisibleViewControllerFrom(nc.visibleViewController)
        } else if let tc = vc as? UITabBarController {
            return UIWindow.getVisibleViewControllerFrom(tc.selectedViewController)
        } else {
            if let pvc = vc?.presentedViewController {
                return UIWindow.getVisibleViewControllerFrom(pvc)
            } else {
                return vc
            }
        }
    }
}

func getTopViewController() -> UIViewController? {
    let appDelegate = UIApplication.shared.delegate
    if let window = appDelegate!.window {
        return window?.visibleViewController
    }
    return nil
}

Use it anywhere as:

if let topVC = getTopViewController() {

}
Rowboat answered 3/3, 2016 at 13:40 Comment(1)
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?.visibleViewControllerBanditry
I
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!

Indigence answered 8/5, 2013 at 14:52 Comment(3)
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.Indigence
@Indigence 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?Vespasian
@Vespasian In situations where it's not immediately clear which class that self should belong to.Palawan
I
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()
Illegitimate answered 1/5, 2019 at 7:19 Comment(1)
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.Antemortem
T
9
@implementation UIWindow (Extensions)

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

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

    return topController;
}

@end
Tatting answered 3/8, 2012 at 9:59 Comment(1)
I don't think you satisfied the condition stated in the original post.Flexuosity
V
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;
}
Varese answered 31/10, 2014 at 7:38 Comment(1)
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.Neogothic
S
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
}
Stereoisomer answered 24/2, 2015 at 14:54 Comment(0)
F
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;
    }
}
Frohman answered 12/5, 2014 at 7:25 Comment(1)
I used this, but note that it breaks when there is more than one presented view controllerInspired
R
6

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!

Request answered 8/2, 2019 at 5:18 Comment(1)
'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 scenesArtificer
M
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()
Mulhouse answered 19/12, 2018 at 15:31 Comment(0)
A
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)

}
Anaglyph answered 4/6, 2016 at 10:37 Comment(0)
P
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;
 }
Pedicure answered 13/3, 2014 at 7:0 Comment(0)
B
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.

Borreri answered 26/9, 2014 at 16:17 Comment(0)
H
3

Alternative Swift solution:

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

    return topController!
}
Heigho answered 8/3, 2015 at 22:0 Comment(0)
L
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
}
Longish answered 21/12, 2016 at 20:15 Comment(0)
C
3

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://mcmap.net/q/107569/-how-to-find-topmost-view-controller-on-ios/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;
};
Clew answered 22/10, 2017 at 14:54 Comment(0)
G
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
}
Grownup answered 3/7, 2015 at 22:45 Comment(0)
E
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
    }
Ebullition answered 9/1, 2020 at 7:48 Comment(0)
E
1

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)
        }
Elephantiasis answered 19/12, 2014 at 15:7 Comment(0)
O
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;
}
Oldwife answered 27/11, 2017 at 21:25 Comment(0)
U
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]; 
Ultraviolet answered 29/10, 2013 at 19:31 Comment(0)
W
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;
}
Withdrawn answered 26/2, 2014 at 4:14 Comment(0)
T
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;
}
Tamanaha answered 7/12, 2014 at 1:52 Comment(0)
M
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.

Mayorga answered 31/3, 2015 at 4:59 Comment(2)
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]Flexuosity
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 beforeMayorga
U
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
}
Uncommon answered 23/4, 2015 at 8:16 Comment(0)
S
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;
    }
}
School answered 17/5, 2015 at 3:59 Comment(0)
M
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.

Modernize answered 16/10, 2015 at 4:52 Comment(0)
H
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
    }
Homeland answered 30/5, 2016 at 14:9 Comment(0)
J
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
    }
}
Juli answered 30/6, 2016 at 13:38 Comment(0)
B
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.

Boilermaker answered 21/10, 2018 at 18:48 Comment(0)
P
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
}
}
Peking answered 28/1, 2019 at 15:43 Comment(0)
Y
0

Swift 5

try this

let topVisibleVC = UIApplication.shared.keyWindow?.rootViewController?.visibleViewController
Youlandayoulton answered 19/10, 2022 at 17:54 Comment(0)
T
-1

you could find the top most view controller by using

NSArray *arrViewControllers=[[self navigationController] viewControllers];
UIViewController *topMostViewController=(UIViewController *)[arrViewControllers objectAtIndex:[arrViewControllers count]-1];
Trilogy answered 26/2, 2014 at 4:50 Comment(1)
Except that, if you actually read the question, self has no navigationController property.Flexuosity
C
-2

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.
Cybill answered 4/2, 2014 at 16:32 Comment(0)
S
-3

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.

Sucking answered 11/9, 2014 at 3:48 Comment(1)
This would be better as an answer if it explained why it is better than the many other answers to this question..Devaluation

© 2022 - 2024 — McMap. All rights reserved.