可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
Is there a way to present a view controller modally without knowing what the visible view controller view is? Basically sort of like you would show an alert view at any points in time.
I would like to be able to do something like:
MyViewController *myVC = [[MyViewController alloc] init];
[myVC showModally];
I'd like to be able to call this from anywhere in the app, and have it appear on top. I don't want to care about what the current view controller is.
I plan to use this to show a login prompt. I don't want to use an alert view, and I also don't want to have login presentation code throughout the app.
Any thoughts on this? Or is there maybe a better way to achieve this? Should I just implement my own mechanism and just place a view on top of the window?
回答1:
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]
.
回答2:
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)
}
}
}
回答3:
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
}
}
}
回答4:
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.
回答5:
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