I'm trying to have an helper class that presents an UIAlertController
. Since it's a helper class, I want it to work regardless of the view hierarchy, and with no information about it. I'm able to show the alert, but when it's being dismissed, the app crashed with:
我正在尝试一个提供UIAlertController的帮助器类。因为它是一个帮助类,所以我希望它不管视图层次结构如何都可以工作,并且没有关于它的信息。我能够显示警报,但当它被解雇时,应用程序崩溃了:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException',
reason: 'Trying to dismiss UIAlertController <UIAlertController: 0x135d70d80>
with unknown presenter.'
I'm creating the popup with:
我正在创建弹出窗口:
guard let window = UIApplication.shared.keyWindow else { return }
let view = UIView()
view.isUserInteractionEnabled = true
window.insertSubview(view, at: 0)
window.bringSubview(toFront: view)
// add full screen constraints to view ...
let controller = UIAlertController(
title: "confirm deletion?",
message: ":)",
preferredStyle: .alert
)
let deleteAction = UIAlertAction(
title: "yes",
style: .destructive,
handler: { _ in
DispatchQueue.main.async {
view.removeFromSuperview()
completion()
}
}
)
controller.addAction(deleteAction)
view.insertSubview(controller.view, at: 0)
view.bringSubview(toFront: controller.view)
// add centering constraints to controller.view ...
When I tap yes
, the app will crash and the handler is not being hit before the crash. I can't present the UIAlertController
because this would be dependent of the current view hierarchy, while I want the popup to be independant
当我点击是时,应用程序将崩溃并且崩溃前处理程序未被命中。我无法呈现UIAlertController,因为这将取决于当前的视图层次结构,而我希望弹出窗口是独立的
EDIT: Swift solution Thanks @Vlad for the idea. It seems that operating in a separate window is much more simple. So here is a working Swift solution:
编辑:Swift解决方案感谢@Vlad的想法。看起来在单独的窗口中操作要简单得多。所以这是一个有效的Swift解决方案:
class Popup {
private var alertWindow: UIWindow
static var shared = Popup()
init() {
alertWindow = UIWindow(frame: UIScreen.main.bounds)
alertWindow.rootViewController = UIViewController()
alertWindow.windowLevel = UIWindowLevelAlert + 1
alertWindow.makeKeyAndVisible()
alertWindow.isHidden = true
}
private func show(completion: @escaping ((Bool) -> Void)) {
let controller = UIAlertController(
title: "Want to do it?",
message: "message",
preferredStyle: .alert
)
let yesAction = UIAlertAction(
title: "Yes",
style: .default,
handler: { _ in
DispatchQueue.main.async {
self.alertWindow.isHidden = true
completion(true)
}
})
let noAction = UIAlertAction(
title: "Not now",
style: .destructive,
handler: { _ in
DispatchQueue.main.async {
self.alertWindow.isHidden = true
completion(false)
}
})
controller.addAction(noAction)
controller.addAction(yesAction)
self.alertWindow.isHidden = false
alertWindow.rootViewController?.present(controller, animated: false)
}
}
4 个解决方案
#1
32
Here's a Swift 3 extension for it:
这是一个Swift 3扩展:
public extension UIAlertController {
func show() {
let win = UIWindow(frame: UIScreen.main.bounds)
let vc = UIViewController()
vc.view.backgroundColor = .clear
win.rootViewController = vc
win.windowLevel = UIWindowLevelAlert + 1
win.makeKeyAndVisible()
vc.present(self, animated: true, completion: nil)
}
}
Just setup your UIAlertController, and then call:
只需设置您的UIAlertController,然后调用:
alert.show()
No more bound by the View Controllers hierarchy!
不再受View Controllers层次结构的约束!
#2
9
I will rather present it on UIApplication.shared.keyWindow.rootViewController, instead of using your logic. So you can do next:
我宁愿把它呈现在UIApplication.shared.keyWindow.rootViewController上,而不是使用你的逻辑。所以你可以做下一个:
UIApplication.shared.keyWindow.rootViewController.presentController(yourAlert, animated: true, completion: nil)
EDITED:
编辑:
I have an old ObjC category, where I've used the next method show, which I used, if no controller was provided to present from:
我有一个旧的ObjC类别,我使用了下一个方法show,我使用过,如果没有提供控制器来呈现:
- (void)show
{
self.alertWindow = [[UIWindow alloc] initWithFrame: [UIScreen mainScreen].bounds];
self.alertWindow.rootViewController = [UIViewController new];
self.alertWindow.windowLevel = UIWindowLevelAlert + 1;
[self.alertWindow makeKeyAndVisible];
[self.alertWindow.rootViewController presentViewController: self animated: YES completion: nil];
}
added entire category, if somebody need it
添加整个类别,如果有人需要它
#import "UIAlertController+ShortMessage.h"
#import <objc/runtime.h>
@interface UIAlertController ()
@property (nonatomic, strong) UIWindow* alertWindow;
@end
@implementation UIAlertController (ShortMessage)
- (void)setAlertWindow: (UIWindow*)alertWindow
{
objc_setAssociatedObject(self, @selector(alertWindow), alertWindow, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (UIWindow*)alertWindow
{
return objc_getAssociatedObject(self, @selector(alertWindow));
}
+ (UIAlertController*)showShortMessage: (NSString*)message fromController: (UIViewController*)controller
{
return [self showAlertWithTitle: nil shortMessage: message fromController: controller];
}
+ (UIAlertController*)showAlertWithTitle: (NSString*)title shortMessage: (NSString*)message fromController: (UIViewController*)controller
{
return [self showAlertWithTitle: title shortMessage: message actions: @[[UIAlertAction actionWithTitle: @"Ok" style: UIAlertActionStyleDefault handler: nil]] fromController: controller];
}
+ (UIAlertController*)showAlertWithTitle: (NSString*)title shortMessage: (NSString*)message actions: (NSArray<UIAlertAction*>*)actions fromController: (UIViewController*)controller
{
UIAlertController* alert = [UIAlertController alertControllerWithTitle: title
message: message
preferredStyle: UIAlertControllerStyleAlert];
for (UIAlertAction* action in actions)
{
[alert addAction: action];
}
if (controller)
{
[controller presentViewController: alert animated: YES completion: nil];
}
else
{
[alert show];
}
return alert;
}
+ (UIAlertController*)showAlertWithMessage: (NSString*)message actions: (NSArray<UIAlertAction*>*)actions fromController: (UIViewController*)controller
{
return [self showAlertWithTitle: @"" shortMessage: message actions: actions fromController: controller];
}
- (void)show
{
self.alertWindow = [[UIWindow alloc] initWithFrame: [UIScreen mainScreen].bounds];
self.alertWindow.rootViewController = [UIViewController new];
self.alertWindow.windowLevel = UIWindowLevelAlert + 1;
[self.alertWindow makeKeyAndVisible];
[self.alertWindow.rootViewController presentViewController: self animated: YES completion: nil];
}
@end
#3
1
Swift 3 example
Swift 3的例子
let alertWindow = UIWindow(frame: UIScreen.main.bounds)
alertWindow.rootViewController = UIViewController()
alertWindow.windowLevel = UIWindowLevelAlert + 1
let alert = UIAlertController(title: "AlertController Tutorial", message: "Submit something", preferredStyle: .alert)
alertWindow.makeKeyAndVisible()
alertWindow.rootViewController?.present(alert, animated: true, completion: nil)
#4
0
func windowErrorAlert(message:String){
let alert = UIAlertController(title: "Error", message: message, preferredStyle: .alert)
let window = UIWindow(frame: UIScreen.main.bounds)
window.rootViewController = UIViewController()
let okAction = UIAlertAction(title: "Ok", style: .default) { (action) -> Void in
alert.dismiss(animated: true, completion: nil)
window.resignKey()
window.isHidden = true
window.removeFromSuperview()
window.windowLevel = UIWindowLevelAlert - 1
window.setNeedsLayout()
}
alert.addAction(okAction)
window.windowLevel = UIWindowLevelAlert + 1
window.makeKeyAndVisible()
window.rootViewController?.present(alert, animated: true, completion: nil)
}
Create a UIAlertController on top of all view and also dismiss and give focus back to your rootViewController.
在所有视图之上创建一个UIAlertController,同时关闭并将焦点重新放回rootViewController。
#1
32
Here's a Swift 3 extension for it:
这是一个Swift 3扩展:
public extension UIAlertController {
func show() {
let win = UIWindow(frame: UIScreen.main.bounds)
let vc = UIViewController()
vc.view.backgroundColor = .clear
win.rootViewController = vc
win.windowLevel = UIWindowLevelAlert + 1
win.makeKeyAndVisible()
vc.present(self, animated: true, completion: nil)
}
}
Just setup your UIAlertController, and then call:
只需设置您的UIAlertController,然后调用:
alert.show()
No more bound by the View Controllers hierarchy!
不再受View Controllers层次结构的约束!
#2
9
I will rather present it on UIApplication.shared.keyWindow.rootViewController, instead of using your logic. So you can do next:
我宁愿把它呈现在UIApplication.shared.keyWindow.rootViewController上,而不是使用你的逻辑。所以你可以做下一个:
UIApplication.shared.keyWindow.rootViewController.presentController(yourAlert, animated: true, completion: nil)
EDITED:
编辑:
I have an old ObjC category, where I've used the next method show, which I used, if no controller was provided to present from:
我有一个旧的ObjC类别,我使用了下一个方法show,我使用过,如果没有提供控制器来呈现:
- (void)show
{
self.alertWindow = [[UIWindow alloc] initWithFrame: [UIScreen mainScreen].bounds];
self.alertWindow.rootViewController = [UIViewController new];
self.alertWindow.windowLevel = UIWindowLevelAlert + 1;
[self.alertWindow makeKeyAndVisible];
[self.alertWindow.rootViewController presentViewController: self animated: YES completion: nil];
}
added entire category, if somebody need it
添加整个类别,如果有人需要它
#import "UIAlertController+ShortMessage.h"
#import <objc/runtime.h>
@interface UIAlertController ()
@property (nonatomic, strong) UIWindow* alertWindow;
@end
@implementation UIAlertController (ShortMessage)
- (void)setAlertWindow: (UIWindow*)alertWindow
{
objc_setAssociatedObject(self, @selector(alertWindow), alertWindow, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (UIWindow*)alertWindow
{
return objc_getAssociatedObject(self, @selector(alertWindow));
}
+ (UIAlertController*)showShortMessage: (NSString*)message fromController: (UIViewController*)controller
{
return [self showAlertWithTitle: nil shortMessage: message fromController: controller];
}
+ (UIAlertController*)showAlertWithTitle: (NSString*)title shortMessage: (NSString*)message fromController: (UIViewController*)controller
{
return [self showAlertWithTitle: title shortMessage: message actions: @[[UIAlertAction actionWithTitle: @"Ok" style: UIAlertActionStyleDefault handler: nil]] fromController: controller];
}
+ (UIAlertController*)showAlertWithTitle: (NSString*)title shortMessage: (NSString*)message actions: (NSArray<UIAlertAction*>*)actions fromController: (UIViewController*)controller
{
UIAlertController* alert = [UIAlertController alertControllerWithTitle: title
message: message
preferredStyle: UIAlertControllerStyleAlert];
for (UIAlertAction* action in actions)
{
[alert addAction: action];
}
if (controller)
{
[controller presentViewController: alert animated: YES completion: nil];
}
else
{
[alert show];
}
return alert;
}
+ (UIAlertController*)showAlertWithMessage: (NSString*)message actions: (NSArray<UIAlertAction*>*)actions fromController: (UIViewController*)controller
{
return [self showAlertWithTitle: @"" shortMessage: message actions: actions fromController: controller];
}
- (void)show
{
self.alertWindow = [[UIWindow alloc] initWithFrame: [UIScreen mainScreen].bounds];
self.alertWindow.rootViewController = [UIViewController new];
self.alertWindow.windowLevel = UIWindowLevelAlert + 1;
[self.alertWindow makeKeyAndVisible];
[self.alertWindow.rootViewController presentViewController: self animated: YES completion: nil];
}
@end
#3
1
Swift 3 example
Swift 3的例子
let alertWindow = UIWindow(frame: UIScreen.main.bounds)
alertWindow.rootViewController = UIViewController()
alertWindow.windowLevel = UIWindowLevelAlert + 1
let alert = UIAlertController(title: "AlertController Tutorial", message: "Submit something", preferredStyle: .alert)
alertWindow.makeKeyAndVisible()
alertWindow.rootViewController?.present(alert, animated: true, completion: nil)
#4
0
func windowErrorAlert(message:String){
let alert = UIAlertController(title: "Error", message: message, preferredStyle: .alert)
let window = UIWindow(frame: UIScreen.main.bounds)
window.rootViewController = UIViewController()
let okAction = UIAlertAction(title: "Ok", style: .default) { (action) -> Void in
alert.dismiss(animated: true, completion: nil)
window.resignKey()
window.isHidden = true
window.removeFromSuperview()
window.windowLevel = UIWindowLevelAlert - 1
window.setNeedsLayout()
}
alert.addAction(okAction)
window.windowLevel = UIWindowLevelAlert + 1
window.makeKeyAndVisible()
window.rootViewController?.present(alert, animated: true, completion: nil)
}
Create a UIAlertController on top of all view and also dismiss and give focus back to your rootViewController.
在所有视图之上创建一个UIAlertController,同时关闭并将焦点重新放回rootViewController。