Get current view controller from the app delegate (modal is possible)
Asked Answered
D

10

7

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?

Dopester answered 10/12, 2013 at 3:39 Comment(1)
Specifically, I'd like to detect the current view controller from - (void)applicationDidEnterBackground:(UIApplication *)application Dopester
N
7

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
Nonattendance answered 10/12, 2013 at 5:48 Comment(5)
I love you! I've been stuck with this problem forever now and your solution fixed it! Thank you!Disvalue
You 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.Wina
What 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.Wina
@Wina I think applicationWillResignActive is called before applicationDidEnterBackground. And I like your idea and will use it!Jetty
@Jetty yes I guess it's called first so as it's mentioned here: developer.apple.com/library/ios/documentation/UIKit/Reference/… :)Wina
S
9

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
Slaby answered 14/11, 2014 at 7:41 Comment(2)
I think you need to switch .h and .m :)Dwelling
Awesome answer, I believe the new syntax however has changed and topMostViewController is no longer a property at least for Swift 2.Diarmit
N
7

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
Nonattendance answered 10/12, 2013 at 5:48 Comment(5)
I love you! I've been stuck with this problem forever now and your solution fixed it! Thank you!Disvalue
You 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.Wina
What 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.Wina
@Wina I think applicationWillResignActive is called before applicationDidEnterBackground. And I like your idea and will use it!Jetty
@Jetty yes I guess it's called first so as it's mentioned here: developer.apple.com/library/ios/documentation/UIKit/Reference/… :)Wina
A
5

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;
    }
}
Apostrophe answered 3/7, 2015 at 22:50 Comment(1)
How does this handle UISplitViewController ? Can't test it right now, but I'm afraid it won't work??Visualize
P
4

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!")    
   }
}
Parlando answered 23/4, 2015 at 19:55 Comment(5)
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....Fining
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.Parlando
Thanks for reply but I solved the problem now, my problem was #30175195 but some users of stackoverflow decided to close it :D, Thanks anywayFining
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.Diarmit
@Unome, thanks and yes you are right. I use window variable throughout AppDelegate for other reasons so, I threw that up there.Parlando
T
3

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.

Take answered 10/12, 2013 at 3:53 Comment(0)
P
1

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.

Pend answered 10/12, 2013 at 3:54 Comment(1)
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 ?Hyams
H
1

In swift you can get the active ViewController like this :

 let navigationController = application.windows[0].rootViewController as UINavigationController

 let activeViewCont = navigationController.visibleViewController
Hyams answered 7/1, 2015 at 9:41 Comment(0)
V
1

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")
    }
}
Visualize answered 16/8, 2016 at 14:59 Comment(0)
B
0

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
    }
}
Bleeder answered 22/7, 2015 at 7:34 Comment(0)
D
0

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

}

Deloris answered 21/2, 2016 at 14:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.