Ios - UIAlertController is present on top of everything, regardless of the hierarchy of views

I am trying to have a helper class that represents a UIAlertController . Since this is a helper class, I want it to work regardless of the hierarchy of views and without information about it. I can show a warning, but when it is fired, the application crashed into:

 *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Trying to dismiss UIAlertController <UIAlertController: 0x135d70d80> with unknown presenter.' 

I am creating a popup using:

 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 click yes , the application crashes and the handler does not hit before the crash. I can’t imagine the UIAlertController because it will depend on the current view hierarchy, while I want the popup to be independent

EDIT: quick fix Thanks to @Vlad for this idea. It seems that working in a separate window is much easier. So here is a working Swift solution:

 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) } } 
+30
ios swift popup crash
source share
8 answers

July 23, 2019 Patch:

Apparently, this technique stopped working in iOS 13.0. :(

I will update as soon as I find the time to investigate ...

Old technique:

Here is the Swift (5) extension for it:

 public extension UIAlertController { func show() { let win = UIWindow(frame: UIScreen.main.bounds) let vc = UIViewController() vc.view.backgroundColor = .clear win.rootViewController = vc win.windowLevel = UIWindow.Level.alert + 1 // Swift 3-4: UIWindowLevelAlert + 1 win.makeKeyAndVisible() vc.present(self, animated: true, completion: nil) } } 

Just configure your UIAlertController, and then call:

 alert.show() 

No longer limited to the View Controllers hierarchy!

+82
source share

I would rather introduce it to UIApplication.shared.keyWindow.rootViewController, instead of using your logic. So you can do the following:

 UIApplication.shared.keyWindow.rootViewController.presentController(yourAlert, animated: true, completion: nil) 

Edition:

I have an old ObjC category where I used the following show method, which I used if no controller was provided for presentation from:

 - (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]; } 

a whole category is added if someone needs 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 
+15
source share

The old approach with the addition of the show() method and the local UIWindow instance no longer works on iOS 13 (the window closes immediately).

Here is the UIAlertController Swift extension that should work on iOS 13 :

 import UIKit private var associationKey: UInt8 = 0 extension UIAlertController { private var alertWindow: UIWindow! { get { return objc_getAssociatedObject(self, &associationKey) as? UIWindow } set(newValue) { objc_setAssociatedObject(self, &associationKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN) } } func show() { self.alertWindow = UIWindow.init(frame: UIScreen.main.bounds) self.alertWindow.backgroundColor = .red let viewController = UIViewController() viewController.view.backgroundColor = .green self.alertWindow.rootViewController = viewController let topWindow = UIApplication.shared.windows.last if let topWindow = topWindow { self.alertWindow.windowLevel = topWindow.windowLevel + 1 } self.alertWindow.makeKeyAndVisible() self.alertWindow.rootViewController?.present(self, animated: true, completion: nil) } override open func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) self.alertWindow.isHidden = true self.alertWindow = nil } } 

Such a UIAlertController can then be created and displayed as follows:

 let alertController = UIAlertController(title: "Title", message: "Message", preferredStyle: .alert) let alertAction = UIAlertAction(title: "Title", style: .default) { (action) in print("Action") } alertController.addAction(alertAction) alertController.show() 
+9
source share

in Swift 4.1 and Xcode 9.4.1

I call the alert function from my common class

 //This is my shared class import UIKit class SharedClass: NSObject { static let sharedInstance = SharedClass() //This is alert function func alertWindow(title: String, message: String) { let alertWindow = UIWindow(frame: UIScreen.main.bounds) alertWindow.rootViewController = UIViewController() alertWindow.windowLevel = UIWindowLevelAlert + 1 let alert2 = UIAlertController(title: title, message: message, preferredStyle: .alert) let defaultAction2 = UIAlertAction(title: "OK", style: .default, handler: { action in }) alert2.addAction(defaultAction2) alertWindow.makeKeyAndVisible() alertWindow.rootViewController?.present(alert2, animated: true, completion: nil) } private override init() { } } 

I call this alert function in my required view controller as follows.

 //I'm calling this function into my second view controller SharedClass.sharedInstance.alertWindow(title:"Title message here", message:"Description message here") 
+8
source share

Swift 3 example

 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
source share

The often cited solution, using the newly created UIWindow as a UIAlertController extension, stopped working on iOS 13 Betas (it seems like iOS no longer refers to UIWindow , so the warning disappears immediately).

The solution below is a bit more complicated, but works in iOS 13.0 and older versions of iOS:

 class GBViewController: UIViewController { var didDismiss: (() -> Void)? override func dismiss(animated flag: Bool, completion: (() -> Void)?) { super.dismiss(animated: flag, completion:completion) didDismiss?() } override var prefersStatusBarHidden: Bool { return true } } class GlobalPresenter { var globalWindow: UIWindow? static let shared = GlobalPresenter() private init() { } func present(controller: UIViewController) { globalWindow = UIWindow(frame: UIScreen.main.bounds) let root = GBViewController() root.didDismiss = { self.globalWindow?.resignKey() self.globalWindow = nil } globalWindow!.rootViewController = root globalWindow!.windowLevel = UIWindow.Level.alert + 1 globalWindow!.makeKeyAndVisible() globalWindow!.rootViewController?.present(controller, animated: true, completion: nil) } } 

Usage

Usage
  let alert = UIAlertController(title: "Alert Test", message: "Alert!", preferredStyle: .alert) alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) GlobalPresenter.shared.present(controller: alert) 
+3
source share
  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 the whole view, and also reject and return focus to your rootViewController.

+1
source share

My own iOS 13 bypass.

Instead of subclassing the UIViewController (Andreas solution) or adding a related object (Maxim solution), I use a very simple UIWindow subclass declared in the UIAlertController category (extension) file:

 @interface AlertWindow : UIWindow @property (nonatomic) UIWindow *autoretain; @end @implementation AlertWindow @end 

Then in the show method:

 AlertWindow *win = [[AlertWindow alloc] initWithFrame:UIScreen.mainScreen.bounds]; win.autoretain = win; … //code as usual 

and finally release:

 - (void)viewWillDisappear:(BOOL)animated { AlertWindow *win = (AlertWindow *)self.view.window; if ([win isKindOfClass:AlertWindow.class]) win.autoretain = nil; [super viewWillDisappear:animated]; } 

I understand that the OP is marked as Swift, and this is ObjC, but it's so easy to adapt ...

0
source share

All Articles