ios - present UIAlertController on top of everything regardless of the view hierarchy ios - present UIAlertController on top of everything regardless of the view hierarchy ios ios

ios - present UIAlertController on top of everything regardless of the view hierarchy


Update Dec 16, 2019:

Just present the view controller/alert from the current top-most view controller. That will work :)

if #available(iOS 13.0, *) {     if var topController = UIApplication.shared.keyWindow?.rootViewController  {           while let presentedViewController = topController.presentedViewController {                 topController = presentedViewController                }     topController.present(self, animated: true, completion: nil)}

Update July 23, 2019:

IMPORTANT

Apparently the method below this technique stopped working in iOS 13.0 :(

I'll update once I find the time to investigate...

Old technique:

Here's a 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 setup your UIAlertController, and then call:

alert.show()

No more bound by the View Controllers hierarchy!


I will rather present it on UIApplication.shared.keyWindow.rootViewController, instead of using your logic. So you can do next:

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:

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


Old approach with adding show() method and local instance of UIWindow no longer works on iOS 13 (window is dismissed right away).

Here is UIAlertController Swift extension which should work on iOS 13:

import UIKitprivate var associationKey: UInt8 = 0extension 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 UIAlertController then can be created and shown like this:

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()