Presenting a modal controller without knowing the current view controller?

时光怂恿深爱的人放手 提交于 2019-12-02 20:31:49

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

allaire

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

You could have this code implemented in your app delegate:

AppDelegate.m

-(void)presentViewControllerFromVisibleController:(UIViewController *)toPresent
{
    UIViewController *vc = self.window.rootViewController;
    [vc presentViewController:toPresent animated:YES];
}

AppDelegate.h

-(void)presentViewControllerFromVisibleViewController:(UIViewController *)toPresent;

From Wherever

#import "AppDelegate.h"
...
AppDelegate *delegate = [UIApplication sharedApplication].delegate;
[delegate presentViewControllerFromVisibleViewController:myViewControllerToPresent];

In your delegate, you're getting the rootViewController of the window. This will always be visible- it's the 'parent' controller of everything.

I don't think you necessarily need to know which view controller is visible. You can get to the keyWindow of the application and add your modal view controller's view to the top of the list of views. Then you can make it work like the UIAlertView.

Interface file: MyModalViewController.h

#import <UIKit/UIKit.h>

@interface MyModalViewController : UIViewController
- (void) show;
@end

Implementation file: MyModalViewController.m

#import "MyModalViewController.h"


@implementation MyModalViewController

- (void) show {
    UIWindow *window = [[UIApplication sharedApplication] keyWindow];
    //  Configure the frame of your modal's view.
    [window addSubview: self.view];
}

@end
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!