Presenting a modal controller without knowing the current view controller? Presenting a modal controller without knowing the current view controller? ios ios

Presenting a modal controller without knowing the current view controller?


Well, you can follow the chain.

Start at [UIApplication sharedApplication].delegate.window.rootViewController.

At each view controller perform the following series of test.

If [viewController isKindOfClass:[UINavigationController class]], then proceed to [(UINavigationController *)viewController topViewController].

If [viewController isKindOfClass:[UITabBarController class]], then proceed to [(UITabBarController *)viewController selectedViewController].

If [viewController presentedViewController], then proceed to [viewController presentedViewController].


My solution in Swift (inspired by the gist of MartinMoizard)

extension UIViewController {    func presentViewControllerFromVisibleViewController(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)?) {        if let navigationController = self as? UINavigationController {            navigationController.topViewController?.presentViewControllerFromVisibleViewController(viewControllerToPresent, animated: flag, completion: completion)        } else if let tabBarController = self as? UITabBarController {            tabBarController.selectedViewController?.presentViewControllerFromVisibleViewController(viewControllerToPresent, animated: flag, completion: completion)        } else if let presentedViewController = presentedViewController {            presentedViewController.presentViewControllerFromVisibleViewController(viewControllerToPresent, animated: flag, completion: completion)        } else {            present(viewControllerToPresent, animated: flag, completion: completion)        }    }}


This solution gives you the top most view controller so that you can handle any special conditions before presenting from it. For example, maybe you want to present your view controller only if the top most view controller isn't a specific view controller.

extension UIApplication {    /// The top most view controller    static var topMostViewController: UIViewController? {        return UIApplication.shared.keyWindow?.rootViewController?.visibleViewController    }}extension UIViewController {    /// The visible view controller from a given view controller    var visibleViewController: UIViewController? {        if let navigationController = self as? UINavigationController {            return navigationController.topViewController?.visibleViewController        } else if let tabBarController = self as? UITabBarController {            return tabBarController.selectedViewController?.visibleViewController        } else if let presentedViewController = presentedViewController {            return presentedViewController.visibleViewController        } else {            return self        }    }}

With this you can present your view controller from anywhere without needing to know what the top most view controller is

UIApplication.topMostViewController?.present(viewController, animated: true, completion: nil)

Or present your view controller only if the top most view controller isn't a specific view controller

if let topVC = UIApplication.topMostViewController, !(topVC is FullScreenAlertVC) {    topVC.present(viewController, animated: true, completion: nil)}

One thing to note is that if there's a UIAlertController currently being displayed, UIApplication.topMostViewController will return a UIAlertController. Presenting on top of a UIAlertController has weird behavior and should be avoided. As such, you should either manually check that !(UIApplication.topMostViewController is UIAlertController) before presenting, or add an else if case to return nil if self is UIAlertController

extension UIViewController {    /// The visible view controller from a given view controller    var visibleViewController: UIViewController? {        if let navigationController = self as? UINavigationController {            return navigationController.topViewController?.visibleViewController        } else if let tabBarController = self as? UITabBarController {            return tabBarController.selectedViewController?.visibleViewController        } else if let presentedViewController = presentedViewController {            return presentedViewController.visibleViewController        } else if self is UIAlertController {            return nil        } else {            return self        }    }}