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