I know that to get the current view controller from the app delegate, I can use the navigationController
property I have set up for my app. However, it's possible in many places throughout my app that a modal navigation controller could have been presented. Is there any way to detect this from the app delegate, since the current navigation controller will be different from the one to which the app delegate holds a reference?

- 6,893
- 15
- 71
- 115
-
Specifically, I'd like to detect the current view controller from `- (void)applicationDidEnterBackground:(UIApplication *)application ` – Mason Dec 10 '13 at 03:39
10 Answers
Based on the gist here, I made a category to obtain the top most view controller, such that calling [[UIApplication sharedApplication] topMostViewController]
will give you the top most view controller in your app.
This is especially useful in iOS 8 where UIAlertView
and UIActionSheet
have been deprecated in favor of UIAlertController
, which needs to be presented on the top most view controller.
UIViewController+TopMostViewController.h
#import <UIKit/UIKit.h>
@interface UIViewController (TopMostViewController)
- (UIViewController *)topMostViewController;
@end
@interface UIApplication (TopMostViewController)
- (UIViewController *)topMostViewController;
@end
UIViewController+TopMostViewController.m
#import "UIViewController+TopMostViewController.h"
@implementation UIViewController (TopMostViewController)
- (UIViewController *)topMostViewController
{
if (self.presentedViewController == nil)
{
return self;
}
else if ([self.presentedViewController isKindOfClass:[UINavigationController class]])
{
UINavigationController *navigationController = (UINavigationController *)self.presentedViewController;
UIViewController *lastViewController = [[navigationController viewControllers] lastObject];
return [lastViewController topMostViewController];
}
UIViewController *presentedViewController = (UIViewController *)self.presentedViewController;
return [presentedViewController topMostViewController];
}
@end
#pragma mark -
@implementation UIApplication (TopMostViewController)
- (UIViewController *)topMostViewController
{
return [self.keyWindow.rootViewController topMostViewController];
}
@end

- 7,946
- 2
- 26
- 26
-
-
Awesome answer, I believe the new syntax however has changed and topMostViewController is no longer a property at least for Swift 2. – Unome Sep 25 '15 at 16:26
I suggest you use NSNofiticationCenter.
//in AppDelegate:
@interface AppDelegate()
{
...
id lastViewController;
...
}
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
...
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleCurrentViewController) name:@"CurrentViewController" object:nil];
...
}
- (void)handleCurrentViewController:(NSNotification *)notification {
if([[notification userInfo] objectForKey:@"lastViewController"]) {
lastViewController = [[notification userInfo] objectForKey:@"lastViewController"];
}
}
- (void)applicationDidEnterBackground:(UIApplication *)application
{
NSLog(@"last view controller is %@", [(UIViewController *)lastViewController class]);
}
@end
//in every ViewController you want to detect
@implementation SomeViewController
...
- (void) viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[[NSNotificationCenter defaultCenter] postNotificationName:@"CurrentViewController" object:nil userInfo:[NSDictionary dictionaryWithObjectsAndKeys:self, @"lastViewController", nil]];
}
...
@end

- 886
- 7
- 22
-
I love you! I've been stuck with this problem forever now and your solution fixed it! Thank you! – 0xRLA May 07 '14 at 13:07
-
1You press the home button or lock the device, viewWillDisappear will never be called. It directly calls applicationDidEnterBackground. Otherwise you could easily save the last active view in NSUserDefaults. – Maziyar Jun 12 '14 at 14:32
-
2What i do is, each time save the last viewController name in NSUserDefaults field in its viewWillAppear. This way I can access to the last active view user was working with across the app. – Maziyar Jun 16 '14 at 07:32
-
1@Maziyar I think `applicationWillResignActive` is called before `applicationDidEnterBackground`. And I like your idea and will use it! – ToddB Jul 16 '15 at 20:38
-
@ToddB yes I guess it's called first so as it's mentioned here: https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIApplicationDelegate_Protocol/ :) – Maziyar Jul 17 '15 at 12:11
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
}
Objective - C
- (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;
}
}

- 71
- 1
- 3
-
How does this handle `UISplitViewController` ? Can't test it right now, but I'm afraid it won't work?? – Andrej Aug 16 '16 at 12:56
This worked for me. I have many targets that have different controllers so previous answers didn't seemed to work.
first you want this inside your AppDelegate class:
var window: UIWindow?
then, in your function
let navigationController = window?.rootViewController as? UINavigationController
if let activeController = navigationController!.visibleViewController {
if activeController.isKindOfClass( MyViewController ) {
println("I have found my controller!")
}
}

- 4,431
- 43
- 36
-
while I am writing this code in function in other file it gives me error `use of unresolved identifire 'window'` at line one `let navi....` – Varun Naharia May 11 '15 at 19:30
-
Varun Naharia, Sounds like function is outside of AppDelegate class. If you put "var window" and function inside you shouldn't be getting that error. – CodeOverRide May 12 '15 at 22:43
-
Thanks for reply but I solved the problem now, my problem was http://stackoverflow.com/questions/30174695/how-to-convert-objective-c-to-swift-viewforindicator-function but some users of stackoverflow decided to close it :D, Thanks anyway – Varun Naharia May 12 '15 at 22:49
-
Great job! I found however that you can simply call self.window?.rootViewController, you don't need to have the window variable at the top. – Unome Sep 25 '15 at 16:29
-
@Unome, thanks and yes you are right. I use **window** variable throughout AppDelegate for other reasons so, I threw that up there. – CodeOverRide Oct 01 '15 at 18:19
You could try getting the top most view by doing:
[[[[UIApplication sharedApplication] keyWindow] subviews] lastObject];
although this view might be invisible or even covered by some of its subviews...
It depends on your UI but it might help.

- 5,041
- 7
- 33
- 59
If you have the navigation controller in the App Delegate, just use the visibleViewController property. It will give you the visible controller, even if it's a modal.

- 1,733
- 1
- 9
- 13
-
could you precise "if you have the navigation controller" ? Do we need to create an instance of UINavigationController in appDelegate and the acces the visibleViewController using .visibleViewController like this : var navVC = UINavigationController() and then navVC.visibleViewController ? – jmcastel Jan 07 '15 at 09:20
In swift you can get the active ViewController like this :
let navigationController = application.windows[0].rootViewController as UINavigationController
let activeViewCont = navigationController.visibleViewController

- 1,365
- 7
- 17
- 34
Other solutions above work only partially as complex view hierarchies are not handled (navigation controller integrated in tab bar controller, split view controllers, view containers and also alert controllers could mess things up).
I solve this by keeping a reference of the current view controller in AppDelegate. Every time the view appears I take advantage of
viewDidAppear(animated:)
and set the reference in the app delegate.
I know the question was about Objective-C, but I can provide only Swift code and I'm sure it will be useful to both types of users.
First: I've implemented a protocol to keep things clean and reusable:
protocol UpdatableViewController {
func updateUI()
}
Second: I've added a reference to AppDelegate:
var currentViewController: UpdatableViewController?
Third: I set the current view controller in viewDidAppear():
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
let appDelegate = UIApplication.sharedApplication().delegate as? AppDelegate
appDelegate?.currentViewController = self
}
Fourth:
extension ViewController1: UpdatableViewController {
func updateUI() {
print("Implement updating here")
}
}

- 7,266
- 4
- 38
- 57
The best solution, works also with moreNavigationController
in UITabBarController
:
extension UIApplication {
class func topViewController(base: UIViewController? = UIApplication.sharedApplication().keyWindow?.rootViewController) -> UIViewController? {
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 where 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
}
}

- 59,234
- 49
- 233
- 358
Below code works very well.
+(UIViewController*) findBestViewController:(UIViewController*)vc {
if (vc.presentedViewController) {
// Return presented view controller
return [AppDelegate findBestViewController:vc.presentedViewController];
} else if ([vc isKindOfClass:[UISplitViewController class]]) {
// Return right hand side
UISplitViewController* svc = (UISplitViewController*) vc;
if (svc.viewControllers.count > 0)
return [AppDelegate findBestViewController:svc.viewControllers.lastObject];
else
return vc;
} else if ([vc isKindOfClass:[UINavigationController class]]) {
// Return top view
UINavigationController* svc = (UINavigationController*) vc;
if (svc.viewControllers.count > 0)
return [AppDelegate findBestViewController:svc.topViewController];
else
return vc;
} else if ([vc isKindOfClass:[UITabBarController class]]) {
// Return visible view
UITabBarController* svc = (UITabBarController*) vc;
if (svc.viewControllers.count > 0)
return [AppDelegate findBestViewController:svc.selectedViewController];
else
return vc;
} else {
// Unknown view controller type, return last child view controller
return vc;
}
}
+(UIViewController*) currentViewController {
// Find best view controller
UIViewController* viewController = [UIApplication sharedApplication].keyWindow.rootViewController;
return [AppDelegate findBestViewController:viewController];
}

- 25
- 5