不在视图控制器中时如何呈现UIAlertController?
场景:用户点击视图控制器上的按钮。 视图控制器是最上面的(显然)在导航堆栈中。 轻敲调用另一个类上调用的实用程序类方法。 在那里发生了一件坏事,我想在控制返回到视图控制器之前在那里显示警报。
+ (void)myUtilityMethod { // do stuff // something bad happened, display an alert. }
这是可能的与UIAlertView
(但可能不是很合适)。
在这种情况下,如何在myUtilityMethod
显示一个myUtilityMethod
?
在WWDC,我停在其中一个实验室,问苹果工程师这个同样的问题:“显示UIAlertController
的最佳做法是UIAlertController
? 而且他说他们一直在讨论这个问题,我们开玩笑说他们应该有一个会议。 他表示,苹果内部正在创建一个带有透明UIViewController
的UIWindow
,然后在UIAlertController
上展示UIAlertController
。 基本上Dylan Betterman的答案是什么。
但是我不想使用UIAlertController
的子类,因为这需要我在整个应用程序中更改我的代码。 所以在关联对象的帮助下,我在UIAlertController
上做了一个类,它在Objective-C中提供了一个show
方法。
这是相关的代码:
#import "UIAlertController+Window.h" #import <objc/runtime.h> @interface UIAlertController (Window) - (void)show; - (void)show:(BOOL)animated; @end @interface UIAlertController (Private) @property (nonatomic, strong) UIWindow *alertWindow; @end @implementation UIAlertController (Private) @dynamic alertWindow; - (void)setAlertWindow:(UIWindow *)alertWindow { objc_setAssociatedObject(self, @selector(alertWindow), alertWindow, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (UIWindow *)alertWindow { return objc_getAssociatedObject(self, @selector(alertWindow)); } @end @implementation UIAlertController (Window) - (void)show { [self show:YES]; } - (void)show:(BOOL)animated { self.alertWindow = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; self.alertWindow.rootViewController = [[UIViewController alloc] init]; id<UIApplicationDelegate> delegate = [UIApplication sharedApplication].delegate; // Applications that does not load with UIMainStoryboardFile might not have a window property: if ([delegate respondsToSelector:@selector(window)]) { // we inherit the main window's tintColor self.alertWindow.tintColor = delegate.window.tintColor; } // window level is above the top window (this makes the alert, if it's a sheet, show over the keyboard) UIWindow *topWindow = [UIApplication sharedApplication].windows.lastObject; self.alertWindow.windowLevel = topWindow.windowLevel + 1; [self.alertWindow makeKeyAndVisible]; [self.alertWindow.rootViewController presentViewController:self animated:animated completion:nil]; } - (void)viewDidDisappear:(BOOL)animated { [super viewDidDisappear:animated]; // precaution to insure window gets destroyed self.alertWindow.hidden = YES; self.alertWindow = nil; } @end
以下是一个示例用法:
// need local variable for TextField to prevent retain cycle of Alert otherwise UIWindow // would not disappear after the Alert was dismissed __block UITextField *localTextField; UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Global Alert" message:@"Enter some text" preferredStyle:UIAlertControllerStyleAlert]; [alert addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { NSLog(@"do something with text:%@", localTextField.text); // do NOT use alert.textfields or otherwise reference the alert in the block. Will cause retain cycle }]]; [alert addTextFieldWithConfigurationHandler:^(UITextField *textField) { localTextField = textField; }]; [alert show];
创建的UIWindow
在UIAlertController
被处理时将被销毁,因为它是唯一保留UIWindow
对象。 但是,如果将UIAlertController
分配给某个属性,或者通过访问其中一个操作块中的警报来增加其保留计数,则UIWindow
将保留在屏幕上,并锁定您的UI。 请参阅上面的示例使用代码,以避免在需要访问UITextField
的情况下。
我用一个测试项目做了一个GitHub 仓库 : FFGlobalAlertController
你可以用Swift 2.2来做下面的事情:
let alertController: UIAlertController = ... UIApplication.sharedApplication().keyWindow?.rootViewController?.presentViewController(alertController, animated: true, completion: nil)
和Swift 3.0:
let alertController: UIAlertController = ... UIApplication.shared.keyWindow?.rootViewController?.present(alertController, animated: true, completion: nil)
Objective-C的
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Title" message:@"message" preferredStyle:UIAlertControllerStyleAlert]; //... id rootViewController = [UIApplication sharedApplication].delegate.window.rootViewController; if([rootViewController isKindOfClass:[UINavigationController class]]) { rootViewController = ((UINavigationController *)rootViewController).viewControllers.firstObject; } if([rootViewController isKindOfClass:[UITabBarController class]]) { rootViewController = ((UITabBarController *)rootViewController).selectedViewController; } [rootViewController presentViewController:alertController animated:YES completion:nil];
Swift 2.3
let alertController = UIAlertController(title: "title", message: "message", preferredStyle: .Alert) //... var rootViewController = UIApplication.sharedApplication().keyWindow?.rootViewController if let navigationController = rootViewController as? UINavigationController { rootViewController = navigationController.viewControllers.first } if let tabBarController = rootViewController as? UITabBarController { rootViewController = tabBarController.selectedViewController } rootViewController?.presentViewController(alertController, animated: true, completion: nil)
Swift 3
let alertController = UIAlertController(title: "title", message: "message", preferredStyle: .alert) //... var rootViewController = UIApplication.shared.keyWindow?.rootViewController if let navigationController = rootViewController as? UINavigationController { rootViewController = navigationController.viewControllers.first } if let tabBarController = rootViewController as? UITabBarController { rootViewController = tabBarController.selectedViewController } rootViewController?.present(alertController, animated: true, completion: nil)
对于UINavigationController
和/或UITabBarController
所有情况,都是相当通用的UIAlertController
extension
。 如果屏幕上有一个模态的VC,那么也可以使用。
用法:
//option 1: myAlertController.show() //option 2: myAlertController.present(animated: true) { //completion code... }
这是延伸:
//Uses Swift1.2 syntax with the new if-let // so it won't compile on a lower version. extension UIAlertController { func show() { present(animated: true, completion: nil) } func present(#animated: Bool, completion: (() -> Void)?) { if let rootVC = UIApplication.sharedApplication().keyWindow?.rootViewController { presentFromController(rootVC, animated: animated, completion: completion) } } private func presentFromController(controller: UIViewController, animated: Bool, completion: (() -> Void)?) { if let navVC = controller as? UINavigationController, let visibleVC = navVC.visibleViewController { presentFromController(visibleVC, animated: animated, completion: completion) } else { if let tabVC = controller as? UITabBarController, let selectedVC = tabVC.selectedViewController { presentFromController(selectedVC, animated: animated, completion: completion) } else { controller.presentViewController(self, animated: animated, completion: completion) } } } }
几个月前我发布了一个类似的问题 ,并认为我终于解决了这个问题。 按照我的帖子底部的链接,如果你只是想看到的代码。
解决方案是使用额外的UIWindow。
当你想显示你的UIAlertController时:
- 使你的窗口的关键和可见的窗口(
window.makeKeyAndVisible()
) - 只需使用一个普通的UIViewController实例作为新窗口的rootViewController。 (
window.rootViewController = UIViewController()
) - 在窗口的rootViewController上展示你的UIAlertController
有几件事要注意:
- 你的UIWindow必须被强引用。 如果没有强烈引用,它将永远不会出现(因为它被释放)。 我建议使用一个属性,但我也有一个关联的对象的成功。
- 为了确保窗口出现在其他所有内容(包括系统UIAlertControllers)上,我设置了windowLevel。 (
window.windowLevel = UIWindowLevelAlert + 1
)
最后,如果你只是想看看,我已经完成了一个实现。
提高agilityvision的答案 ,您需要创建一个透明的根视图控制器的窗口,并从那里提出警报视图。
但是 ,只要您在警报控制器中有一个操作, 就不需要保留对该窗口的引用 。 作为动作处理程序块的最后一步,您只需要将该窗口隐藏为清理任务的一部分。 通过对处理程序块中的窗口进行引用,会创建一个临时的循环引用,一旦警报控制器被解散,就会被打断。
UIWindow* window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; window.rootViewController = [UIViewController new]; window.windowLevel = UIWindowLevelAlert + 1; UIAlertController* alertCtrl = [UIAlertController alertControllerWithTitle:... message:... preferredStyle:UIAlertControllerStyleAlert]; [alertCtrl addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"OK",@"Generic confirm") style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { ... // do your stuff // very important to hide the window afterwards. // this also keeps a reference to the window until the action is invoked. window.hidden = YES; }]]; [window makeKeyAndVisible]; [window.rootViewController presentViewController:alertCtrl animated:YES completion:nil];
即使所有版本看起来相当有希望,以下解决方案也不起作用。 此解决方案正在生成警告 。
警告:尝试呈现其视图不在窗口层次结构中的视图!
https://stackoverflow.com/a/34487871/2369867 =>这看起来很有希望。 但这不是在Swift 3
。 所以我在Swift 3中回答了这个问题,这不是模板的例子。
一旦你粘贴到任何函数里面,这个代码就是完全的功能代码。
Quick
Swift 3
自包含代码
let alertController = UIAlertController(title: "<your title>", message: "<your message>", preferredStyle: UIAlertControllerStyle.alert) alertController.addAction(UIAlertAction(title: "Close", style: UIAlertActionStyle.cancel, handler: nil)) let alertWindow = UIWindow(frame: UIScreen.main.bounds) alertWindow.rootViewController = UIViewController() alertWindow.windowLevel = UIWindowLevelAlert + 1; alertWindow.makeKeyAndVisible() alertWindow.rootViewController?.present(alertController, animated: true, completion: nil)
这是在Swift 3中测试和工作的代码。
添加到Zev的答案(并切换回Objective-C),你可能会遇到一种情况,你的根视图控制器通过segue或其他东西呈现其他VC。 在根VC上调用presentsViewController将处理这个问题:
[[UIApplication sharedApplication].keyWindow.rootViewController.presentedViewController presentViewController:alertController animated:YES completion:^{}];
这样就解决了我所在的VC根植于另一个VC的问题,而不是提出警报控制器,而是发布了一个如上所述的警告:
Warning: Attempt to present <UIAlertController: 0x145bfa30> on <UINavigationController: 0x1458e450> whose view is not in the window hierarchy!
我没有测试过,但是如果你的VC根本就是一个导航控制器的话,这也可能是必须的。
这在Swift中适用于普通的视图控制器,即使屏幕上有一个导航控制器:
let alert = UIAlertController(...) let alertWindow = UIWindow(frame: UIScreen.mainScreen().bounds) alertWindow.rootViewController = UIViewController() alertWindow.windowLevel = UIWindowLevelAlert + 1; alertWindow.makeKeyAndVisible() alertWindow.rootViewController?.presentViewController(alert, animated: true, completion: nil)
创建扩展像在阿维尔格罗斯答案。 这里你有Objective-C扩展。
这里你有头文件* .h
// UIAlertController+Showable.h #import <UIKit/UIKit.h> @interface UIAlertController (Showable) - (void)show; - (void)presentAnimated:(BOOL)animated completion:(void (^)(void))completion; - (void)presentFromController:(UIViewController *)viewController animated:(BOOL)animated completion:(void (^)(void))completion; @end
并执行:* .m
// UIAlertController+Showable.m #import "UIAlertController+Showable.h" @implementation UIAlertController (Showable) - (void)show { [self presentAnimated:YES completion:nil]; } - (void)presentAnimated:(BOOL)animated completion:(void (^)(void))completion { UIViewController *rootVC = [UIApplication sharedApplication].keyWindow.rootViewController; if (rootVC != nil) { [self presentFromController:rootVC animated:animated completion:completion]; } } - (void)presentFromController:(UIViewController *)viewController animated:(BOOL)animated completion:(void (^)(void))completion { if ([viewController isKindOfClass:[UINavigationController class]]) { UIViewController *visibleVC = ((UINavigationController *)viewController).visibleViewController; [self presentFromController:visibleVC animated:animated completion:completion]; } else if ([viewController isKindOfClass:[UITabBarController class]]) { UIViewController *selectedVC = ((UITabBarController *)viewController).selectedViewController; [self presentFromController:selectedVC animated:animated completion:completion]; } else { [viewController presentViewController:self animated:animated completion:completion]; } } @end
你在你的实现文件中使用这个扩展:
#import "UIAlertController+Showable.h" UIAlertController* alert = [UIAlertController alertControllerWithTitle:@"Title here" message:@"Detail message here" preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {}]; [alert addAction:defaultAction]; // Add more actions if needed [alert show];
在Objective-C中提供警报的速记方法:
[[[[UIApplication sharedApplication] keyWindow] rootViewController] presentViewController:alertController animated:YES completion:nil];
alertController
是您的UIAlertController
对象。
注意:您还需要确保您的助手类扩展UIViewController
Zev Eisenberg的答案很简单明了,但并不总是奏效,而且可能会因为这个警告信息而失败:
Warning: Attempt to present <UIAlertController: 0x7fe6fd951e10> on <ThisViewController: 0x7fe6fb409480> which is already presenting <AnotherViewController: 0x7fe6fd109c00>
这是因为窗口rootViewController不在所呈现视图的顶部。 为了纠正这个问题,我们需要走上演示链,如我在Swift 3中编写的UIAlertController扩展代码所示:
/// show the alert in a view controller if specified; otherwise show from window's root pree func show(inViewController: UIViewController?) { if let vc = inViewController { vc.present(self, animated: true, completion: nil) } else { // find the root, then walk up the chain var viewController = UIApplication.shared.keyWindow?.rootViewController var presentedVC = viewController?.presentedViewController while presentedVC != nil { viewController = presentedVC presentedVC = viewController?.presentedViewController } // now we present viewController?.present(self, animated: true, completion: nil) } } func show() { show(inViewController: nil) }
9/15/2017的更新:
经过测试证实,上述逻辑在新推出的iOS 11 GM种子中仍然有效。 然而,agilityvision的顶级投票方法并没有:在新铸造的UIWindow
呈现的警报视图位于键盘下方,并且潜在地阻止了用户点击其按钮。 这是因为在iOS 11中,所有比键盘窗口更高的windowLevel被降低到低于它的水平。
从keyWindow
呈现的一个人造物是当警报出现时滑动键盘的动画,并且当警报被解除时,再次滑动。 如果您希望键盘在演示过程中停留在那里,您可以尝试从顶部窗口本身呈现,如下面的代码所示:
func show(inViewController: UIViewController?) { if let vc = inViewController { vc.present(self, animated: true, completion: nil) } else { // get a "solid" window with the highest level let alertWindow = UIApplication.shared.windows.filter { $0.tintColor != nil || $0.className() == "UIRemoteKeyboardWindow" }.sorted(by: { (w1, w2) -> Bool in return w1.windowLevel < w2.windowLevel }).last // save the top window's tint color let savedTintColor = alertWindow?.tintColor alertWindow?.tintColor = UIApplication.shared.keyWindow?.tintColor // walk up the presentation tree var viewController = alertWindow?.rootViewController while viewController?.presentedViewController != nil { viewController = viewController?.presentedViewController } viewController?.present(self, animated: true, completion: nil) // restore the top window's tint color if let tintColor = savedTintColor { alertWindow?.tintColor = tintColor } } }
上面代码中唯一没有那么重要的地方是它会检查类名UIRemoteKeyboardWindow
,以确保我们也可以包含它。 尽管如此,上面的代码在iOS 9,10和11 GM种子中工作得很好,色彩正确,而且没有键盘滑动工件。
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 } } }
有了这个,你可以像这样轻松地呈现你的警报
UIApplication.topMostViewController?.present(viewController, animated: true, completion: nil)
有一件事要注意的是,如果有一个UIAlertController目前正在显示, UIApplication.topMostViewController
将返回一个UIAlertController
。 呈现在UIAlertController
有奇怪的行为,应该避免。 因此,您应该在呈现之前手动检查!(UIApplication.topMostViewController is UIAlertController)
,或者如果self is UIAlertController
,则添加else if
case以返回nil
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 } } }
您可以将当前视图或控制器作为参数发送:
+ (void)myUtilityMethod:(id)controller { // do stuff // something bad happened, display an alert. }
交叉发表我的答案,因为这两个线程不被标记为愚蠢的…
现在, UIViewController
是响应者链的一部分,你可以做这样的事情:
if let vc = self.nextResponder()?.targetForAction(#selector(UIViewController.presentViewController(_:animated:completion:)), withSender: self) as? UIViewController { let alert = UIAlertController(title: "A snappy title", message: "Something bad happened", preferredStyle: .Alert) alert.addAction(UIAlertAction(title: "OK", style: .Default, handler: nil)) vc.presentViewController(alert, animated: true, completion: nil) }
如果有人有兴趣,我创建了@agilityvision答案的Swift 3版本。 代码:
import Foundation import UIKit extension UIAlertController { var window: UIWindow? { get { return objc_getAssociatedObject(self, "window") as? UIWindow } set { objc_setAssociatedObject(self, "window", newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } open override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) self.window?.isHidden = true self.window = nil } func show(animated: Bool = true) { let window = UIWindow(frame: UIScreen.main.bounds) window.rootViewController = UIViewController(nibName: nil, bundle: nil) let delegate = UIApplication.shared.delegate if delegate?.window != nil { window.tintColor = delegate!.window!!.tintColor } window.windowLevel = UIApplication.shared.windows.last!.windowLevel + 1 window.makeKeyAndVisible() window.rootViewController!.present(self, animated: animated, completion: nil) self.window = window } }
这里是mythicalcoder的答案作为扩展,在Swift 4中进行了测试和工作:
extension UIAlertController { func presentInOwnWindow(animated: Bool, completion: (() -> Void)?) { let alertWindow = UIWindow(frame: UIScreen.main.bounds) alertWindow.rootViewController = UIViewController() alertWindow.windowLevel = UIWindowLevelAlert + 1; alertWindow.makeKeyAndVisible() alertWindow.rootViewController?.present(self, animated: animated, completion: completion) } }
用法示例:
let alertController = UIAlertController(title: "<Alert Title>", message: "<Alert Message>", preferredStyle: .alert) alertController.addAction(UIAlertAction(title: "Close", style: .cancel, handler: nil)) alertController.presentInOwnWindow(animated: true, completion: { print("completed") })
你可以尝试用类似于- (void)presentErrorMessage;
方法在UIViewController
上实现一个类- (void)presentErrorMessage;
而且在这个方法里面你实现了UIAlertController,然后把它展示给self
。 比在你的客户端代码,你会有这样的事情:
[myViewController presentErrorMessage];
这样你就可以避免不必要的参数和警告,而不是在窗口层次结构中。
有两种方法可以使用:
-Use UIAlertView
or 'UIActionSheet' instead (not recommended, cause it deprecated in iOS 8 but it works now)
-Somehow remember the last view controller which is presented. Here is example.
@interface UIViewController (TopController) + (UIViewController *)topViewController; @end // implementation #import "UIViewController+TopController.h" #import <objc/runtime.h> static __weak UIViewController *_topViewController = nil; @implementation UIViewController (TopController) + (UIViewController *)topViewController { UIViewController *vc = _topViewController; while (vc.parentViewController) { vc = vc.parentViewController; } return vc; } + (void)load { [super load]; [self swizzleSelector:@selector(viewDidAppear:) withSelector:@selector(myViewDidAppear:)]; [self swizzleSelector:@selector(viewWillDisappear:) withSelector:@selector(myViewWillDisappear:)]; } - (void)myViewDidAppear:(BOOL)animated { if (_topViewController == nil) { _topViewController = self; } [self myViewDidAppear:animated]; } - (void)myViewWillDisappear:(BOOL)animated { if (_topViewController == self) { _topViewController = nil; } [self myViewWillDisappear:animated]; } + (void)swizzleSelector:(SEL)sel1 withSelector:(SEL)sel2 { Class class = [self class]; Method originalMethod = class_getInstanceMethod(class, sel1); Method swizzledMethod = class_getInstanceMethod(class, sel2); BOOL didAddMethod = class_addMethod(class, sel1, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); if (didAddMethod) { class_replaceMethod(class, sel2, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); } else { method_exchangeImplementations(originalMethod, swizzledMethod); } } @end
用法:
[[UIViewController topViewController] presentViewController:alertController ...];
Kevin Sliech provided a great solution.
I now use the below code in my main UIViewController subclass.
One small alteration i made was to check to see if the best presentation controller is not a plain UIViewController. If not, it's got to be some VC that presents a plain VC. Thus we return the VC that's being presented instead.
- (UIViewController *)bestPresentationController { UIViewController *bestPresentationController = [UIApplication sharedApplication].keyWindow.rootViewController; if (![bestPresentationController isMemberOfClass:[UIViewController class]]) { bestPresentationController = bestPresentationController.presentedViewController; } return bestPresentationController; }
Seems to all work out so far in my testing.
Thank you Kevin!
I use this code with some little personal variations in my AppDelegate class
-(UIViewController*)presentingRootViewController { UIViewController *vc = self.window.rootViewController; if ([vc isKindOfClass:[UINavigationController class]] || [vc isKindOfClass:[UITabBarController class]]) { // filter nav controller vc = [AppDelegate findChildThatIsNotNavController:vc]; // filter tab controller if ([vc isKindOfClass:[UITabBarController class]]) { UITabBarController *tbc = ((UITabBarController*)vc); if ([tbc viewControllers].count > 0) { vc = [tbc viewControllers][tbc.selectedIndex]; // filter nav controller again vc = [AppDelegate findChildThatIsNotNavController:vc]; } } } return vc; } /** * Private helper */ +(UIViewController*)findChildThatIsNotNavController:(UIViewController*)vc { if ([vc isKindOfClass:[UINavigationController class]]) { if (((UINavigationController *)vc).viewControllers.count > 0) { vc = [((UINavigationController *)vc).viewControllers objectAtIndex:0]; } } return vc; }
In addition to great answers given ( agilityvision , adib , malhal ). To reach queueing behaviour like in good old UIAlertViews (avoid alert windows overlap), use this block to observe window level availability:
@interface UIWindow (WLWindowLevel) + (void)notifyWindowLevelIsAvailable:(UIWindowLevel)level withBlock:(void (^)())block; @end @implementation UIWindow (WLWindowLevel) + (void)notifyWindowLevelIsAvailable:(UIWindowLevel)level withBlock:(void (^)())block { UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow; if (keyWindow.windowLevel == level) { // window level is occupied, listen for windows to hide id observer; observer = [[NSNotificationCenter defaultCenter] addObserverForName:UIWindowDidBecomeHiddenNotification object:keyWindow queue:nil usingBlock:^(NSNotification *note) { [[NSNotificationCenter defaultCenter] removeObserver:observer]; [self notifyWindowLevelIsAvailable:level withBlock:block]; // recursive retry }]; } else { block(); // window level is available } } @end
完整的例子:
[UIWindow notifyWindowLevelIsAvailable:UIWindowLevelAlert withBlock:^{ UIWindow *alertWindow = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; alertWindow.windowLevel = UIWindowLevelAlert; alertWindow.rootViewController = [UIViewController new]; [alertWindow makeKeyAndVisible]; UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Alert" message:nil preferredStyle:UIAlertControllerStyleAlert]; [alertController addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) { alertWindow.hidden = YES; }]]; [alertWindow.rootViewController presentViewController:alertController animated:YES completion:nil]; }];
This will allow you to avoid alert windows overlap. Same method can be used to separate and put in queue view controllers for any number of window layers.
Seems to work:
static UIViewController *viewControllerForView(UIView *view) { UIResponder *responder = view; do { responder = [responder nextResponder]; } while (responder && ![responder isKindOfClass:[UIViewController class]]); return (UIViewController *)responder; } -(void)showActionSheet { UIAlertController *alertController = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet]; [alertController addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]]; [alertController addAction:[UIAlertAction actionWithTitle:@"Do it" style:UIAlertActionStyleDefault handler:nil]]; [viewControllerForView(self) presentViewController:alertController animated:YES completion:nil]; }
create helper class AlertWindow and than use as
let alertWindow = AlertWindow(); let alert = UIAlertController(title: "Hello", message: "message", preferredStyle: .alert); let cancel = UIAlertAction(title: "Ok", style: .cancel){(action) in //.... action code here // reference to alertWindow retain it. Every action must have this at end alertWindow.isHidden = true; // here AlertWindow.deinit{ } } alert.addAction(cancel); alertWindow.present(alert, animated: true, completion: nil) class AlertWindow:UIWindow{ convenience init(){ self.init(frame:UIScreen.main.bounds); } override init(frame: CGRect) { super.init(frame: frame); if let color = UIApplication.shared.delegate?.window??.tintColor { tintColor = color; } rootViewController = UIViewController() windowLevel = UIWindowLevelAlert + 1; makeKeyAndVisible() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } deinit{ // semaphor.signal(); } func present(_ ctrl:UIViewController, animated:Bool, completion: (()->Void)?){ rootViewController!.present(ctrl, animated: animated, completion: completion); } }
@agilityvision's answer is so good. I have sense used in swift projects so I thought I would share my take on his answer using swift 3.0
fileprivate class MyUIAlertController: UIAlertController { typealias Handler = () -> Void struct AssociatedKeys { static var alertWindowKey = "alertWindowKey" } dynamic var _alertWindow: UIWindow? var alertWindow: UIWindow? { return objc_getAssociatedObject(self, &AssociatedKeys.alertWindowKey) as? UIWindow } func setAlert(inWindow window: UIWindow) { objc_setAssociatedObject(self, &AssociatedKeys.alertWindowKey, _alertWindow, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } func show(completion: Handler? = nil) { show(animated: true, completion: completion) } func show(animated: Bool, completion: Handler? = nil) { _alertWindow = UIWindow(frame: UIScreen.main.bounds) _alertWindow?.rootViewController = UIViewController() if let delegate: UIApplicationDelegate = UIApplication.shared.delegate, let window = delegate.window { _alertWindow?.tintColor = window?.tintColor } let topWindow = UIApplication.shared.windows.last _alertWindow?.windowLevel = topWindow?.windowLevel ?? 0 + 1 _alertWindow?.makeKeyAndVisible() _alertWindow?.rootViewController?.present(self, animated: animated, completion: completion) } fileprivate override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) _alertWindow?.isHidden = true _alertWindow = nil } }
Register for a notification prior to calling the class method.
Swift code:
NSNotificationCenter.defaultCenter().addObserver(self, selector: "displayAlert", name: "ErrorOccured", object: nil)
In the displayAlert
instance method you could display your alert.
- 上传iOS应用程序时,应用程序加载器停留在“使用iTunes商店authentication”
- 如何在iPhone上绘制“语音泡泡”?
- iOS 10“启动WebFilterlogin进程”+ Proxy-Authorization头从请求中删除
- Grand Central Dispatch(GCD)与performSelector – 需要更好的解释
- 如何检查iOS版本?
- Swift 2:调用可以抛出,但没有用'try'标记,错误不被处理
- 如何在Swift中将Int转换为hexstring
- NSDateFormatter中的“YYYY”和“yyyy”之间的区别
- UITableView中的声明失败configureCellForDisplay:forIndexPath: