292

Scenario: The user taps on a button on a view controller. The view controller is the topmost (obviously) in the navigation stack. The tap invokes a utility class method called on another class. A bad thing happens there and I want to display an alert right there before control returns to the view controller.

+ (void)myUtilityMethod {
    // do stuff
    // something bad happened, display an alert.
}

This was possible with UIAlertView (but perhaps not quite proper).

In this case, how do you present a UIAlertController, right there in myUtilityMethod?

Murray Sagal
  • 8,454
  • 4
  • 47
  • 48

39 Answers39

340

At WWDC, I stopped in at one of the labs and asked an Apple Engineer this same question: "What was the best practice for displaying a UIAlertController?" And he said they had been getting this question a lot and we joked that they should have had a session on it. He said that internally Apple is creating a UIWindow with a transparent UIViewController and then presenting the UIAlertController on it. Basically what is in Dylan Betterman's answer.

But I didn't want to use a subclass of UIAlertController because that would require me changing my code throughout my app. So with the help of an associated object, I made a category on UIAlertController that provides a show method in Objective-C.

Here is the relevant code:

#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 ensure window gets destroyed
    self.alertWindow.hidden = YES;
    self.alertWindow = nil;
}

@end

Here is a sample usage:

// 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];

The UIWindow that is created will be destroyed when the UIAlertController is dealloced, since it is the only object that is retaining the UIWindow. But if you assign the UIAlertController to a property or cause its retain count to increase by accessing the alert in one of the action blocks, the UIWindow will stay on screen, locking up your UI. See the sample usage code above to avoid in the case of needing to access UITextField.

I made a GitHub repo with a test project: FFGlobalAlertController

Community
  • 1
  • 1
agilityvision
  • 7,901
  • 2
  • 29
  • 28
  • 1
    Good stuff! Just some background -- I used a subclass instead of an associated object because I was using Swift. Associated objects are a feature of the Objective-C runtime and I didn't want to be dependent on it. Swift is probably years away from getting it's own runtime, but still. :) – Dylan Bettermann Jun 24 '15 at 03:15
  • 2
    I really like the elegance of your answer, however I'm curious how you retire the new window and make the original window the key again (admittedly I don't muck around with the window much). – Dustin Pfannenstiel Jun 24 '15 at 14:18
  • @DustinPfannenstiel The Alert is the only object that has retained the UIWindow, so when the Alert is dismissed, it then gets dealloced and along with it the UIWindow and so it disappears. So in order for this to work you can't store the Alert in a property or create a retain cycle by using Alert in a UIAlertAction block. – agilityvision Jun 24 '15 at 17:35
  • It's entirely possible I don't understand the role of `makeKeyAndVisible`. If the new window becomes the key window, how does the original window (from the app delegate) become the key window again? – Dustin Pfannenstiel Jun 24 '15 at 20:13
  • 2
    The key window is the topmost visible window, so my understanding is if you remove/hide the "key" window, the next visible window down becomes "key". – agilityvision Jun 24 '15 at 22:47
  • 1
    @agilityvision In my experience that's correct. Do keep in mind that `UIWindow`s are also ordered by the `UIWindowLevel` property. Take a look at the Discussion section for the constant in the documentation: https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIWindow_Class/#//apple_ref/c/tdef/UIWindowLevel – Dylan Bettermann Jun 25 '15 at 19:35
  • I love this solution...it's simple and clean! But I do seem to be having trouble with the key window stuff that Dustin was worried about. After I tap a button to close out the UIAlertController the rest of my app ui appears to be blocked. When I use the 'debug view hierarchy' tool it definitely looks like something is not quite cleaned up with the windows. – toofah Jul 22 '15 at 14:40
  • I tried this in the FFGlobalAlertController example app and although it appears to work correctly the same sort of left over windows are there. Here is the view hierarchy before the alert is shown:http://d.pr/i/11v7d and here is the hierarchy after:http://d.pr/i/Dp5Q – toofah Jul 22 '15 at 14:55
  • That means you are retaining the UIAlertController, and when that happens the UIWindow doesn't get dealloc. See sample usage above for how to handle textfields. I'll update answer with more details. – agilityvision Jul 22 '15 at 15:38
  • Thanks, that was it. I forgot that I was passing back a reference to my alert from the calling method so that I can sometimes programmatically close the alert. I'll have to rethink that. Do you understand why in your example code there is a UITextEffectsWindow present after your alert closes that was no there previously? – toofah Jul 22 '15 at 16:25
  • use a weak reference if you want to be able programmatically close the alert. – agilityvision Jul 23 '15 at 15:17
  • I just compiled with Xcode 7 and now my I am experiencing the same problem where the window seems to be hanging around again. – toofah Aug 27 '15 at 19:48
  • After some more investigation it seems that the new key window created for the alert might not be resigning. In iOS 8 I get UIWindowDidBecomeKeyNotification when the alert closes, but in iOS 9 that notification never fires, signaling to me that my alert window is still around and is still the key window or something. – toofah Sep 02 '15 at 19:04
  • @agilityVision made a fix to FFGlobalAlertController to address the issue of the window now going away. Thanks! He overwrote for the viewDidDisappear method and hid and nil-ed out the window. – toofah Sep 02 '15 at 20:31
  • Calling the `super` doesn't do anything in Category. Because Category extends the class, but they don't subclass. – Yoon Lee Dec 03 '15 at 00:01
  • Does this work for showing two alerts that queue up? That's my main issue with the new class is duplicate alerts just get dropped. – malhal Jan 08 '16 at 22:05
  • 20
    Implementing `viewDidDisappear:` on a category looks like a Bad Idea. In essence, you're competing with the framework's implementation of `viewDidDisappear:`. For now it may be okay, but if Apple decides to implement that method in the future, there's no way for it for you to call it (i.e. there is no analogous of `super` that points to the primary implementation of a method from a category implementation). – adib Jan 23 '16 at 06:11
  • good point, the code in viewDidDisappear is not needed. I added in later after to cover cases where people had a strong reference to the alert and the window wouldn't then get delloced. As I mentioned in my answer. – agilityvision Jan 25 '16 at 14:46
  • I've improved on this concept to add support for queueing up multiple alerts like the original behaviour of UIAlertView, see http://stackoverflow.com/a/35211571/259521 – malhal Feb 04 '16 at 21:04
  • I replaced the line [self.alertWindow makeKeyAndVisible]; with [self.alertWindow setHidden:NO]; – Hassy Mar 12 '16 at 05:26
  • I had problems with this solution in iOS9.3 due to the override of `-viewDidDisappear:`. Things worked fine when debugging but crashed with a release build. I ended up creating a similar solution that *subclassed* `UIAlertController` based on this: https://github.com/kirbyt/WPSKit/blob/master/WPSKit/UIKit/WPSAlertController.h. So far so good. – chris Mar 27 '16 at 17:54
  • FYI, I've created a CocoaPod called WindowAlert that encapsulates the logic you've described. https://github.com/DrBreen/WindowAlert – Alexander Woodblock May 27 '16 at 16:07
  • 5
    Works great, but how to treat `prefersStatusBarHidden` and `preferredStatusBarStyle` without an extra subclass? – Kevin Flachsmann Jun 20 '16 at 22:58
  • I have edited the code to not crash applications that does not load from a main storyboard, hence their `AppDelegate` might not have the `window` property. – Elist Aug 02 '16 at 08:22
  • 1
    This approach fails for `UIAlertControllerStyleActionSheet` on iPad. The action sheet is presented but no way to interact with it, since the source view and source rect are set on the window below the currently new transparent window – Leonid Usov Feb 28 '17 at 13:58
  • 1
    Might not want to create your UIWindow with UIScreen since your window might be smaller than your screen if your app is in split screen mode. – Steve Moser May 25 '17 at 20:42
  • One of my users on an iOS 10.3.1 iPhone 7 managed to get invisible button labels using this hack. No idea why. It's not been found on other devices. Shame I have to remove this from my code. :( – TealShift Jun 16 '17 at 00:37
  • It just occurred to me that the tint color must have been clear on her app delegate's window somehow. Removing that part of the code fixes it. – TealShift Jun 17 '17 at 04:20
  • If you are already using a launch screen storyboard, could also be `[[UIStoryboard storyboardWithName:@"LaunchScreen" bundle: nil] instantiateInitialViewController]` instead of `[[UIViewController alloc] init]` to more closely imitate a launched, running app. – Gary Jul 28 '17 at 10:03
  • I would recommend not presenting the alert by the class method at all. If it's a now 'visual' class, it should report the error back to the caller and let the called decide wether it's worth showing an alert or not. This way it becomes a lot easier to test as well. – D. Mika Feb 25 '18 at 15:42
  • Use a delegate instead! – Andrew Kirna May 16 '19 at 13:26
  • Unless you retaining UIAlertController somewhere in some way `-viewDidDisappear` is not required: all associated objects are released at moment of object destruction. Any visible (or invisible) UIWindow are not retained by UIKit, so, the only reference preventing UIWindow from destruction (and disappearing as result) is created during association. As result, window got removed and destroyed at moment of alert dismissal – Krypt Aug 15 '19 at 17:28
  • 3
    This code doesn't seem to be working right now in iOS 13. Alert doesn't get presented at all in my case. – coolcool1994 Mar 21 '20 at 12:03
128

Swift

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)

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];
Darkngs
  • 6,381
  • 5
  • 25
  • 30
  • 3
    +1 This is a brilliantly simple solution. (Problem I faced: Displaying an alert in the DetailViewController of Master/Detail template - Shows on iPad, never on iPhone) – David Feb 25 '16 at 19:16
  • 9
    Nice, you might want to add in another part: if (rootViewController.presentedViewController != nil) { rootViewController = rootViewController.presentedViewController; } – DivideByZer0 May 25 '16 at 01:27
  • 1
    Swift 3: 'Alert' has been renamed to 'alert': let alertController = UIAlertController(title: "title", message: "message", preferredStyle: .alert) – Kaptain Nov 27 '16 at 11:41
  • 1
    Use a delegate instead! – Andrew Kirna May 16 '19 at 13:27
  • 2
    Swift 5.2: Xcode is now saying that UIApplication.shared.keyWindow has been deprecated since iOS 13.0 – Kaji Mar 13 '21 at 00:02
113

You can do the following with Swift 2.2:

let alertController: UIAlertController = ...
UIApplication.sharedApplication().keyWindow?.rootViewController?.presentViewController(alertController, animated: true, completion: nil)

And Swift 3.0:

let alertController: UIAlertController = ...
UIApplication.shared.keyWindow?.rootViewController?.present(alertController, animated: true, completion: nil)
Zev Eisenberg
  • 8,080
  • 5
  • 38
  • 82
  • 12
    Oops, I accepted before I checked. That code returns the root view controller, which in my case is the navigation controller. It doesn't cause an error but the alert doesn't display. – Murray Sagal Oct 24 '14 at 21:09
  • 24
    And I noticed in the console: `Warning: Attempt to present on whose view is not in the window hierarchy!`. – Murray Sagal Oct 24 '14 at 21:21
  • Where are you running `myUtilityMethod`? You can’t run it before the root view controller has displayed its view and expect to see an alert. If you need to run it when the app loads, try `dispatch_async(dispatch_get_main_queue(), ^{ [MyClass myUtilityMethod] });` – Zev Eisenberg Oct 24 '14 at 21:22
  • I'm several view controllers into the navigation stack. The app loaded long ago. I'll update the question. – Murray Sagal Oct 24 '14 at 21:33
  • You could try logging the navigation controller's view's window. – Zev Eisenberg Oct 24 '14 at 21:34
  • Sure, once I have the root view controller I could inspect the `viewControllers` property and pull out the topmost. But maybe a view controller has been pushed modally on that one. It gets awkward. – Murray Sagal Oct 24 '14 at 21:39
  • @MurraySagal check out my answer. You don't have to worry about which UIViewController or UIWindow is visible if you use your own UIWindow. :) – Dylan Bettermann May 28 '15 at 14:37
  • It's a good method to display a VC when you're using *static* method in Objective C. Static method is mandatory when using *CFNotificationCenterGetDarwinNotifyCenter*. – OhadM Feb 10 '16 at 07:48
  • 1
    @MurraySagal having a navigation controller you can get the `visibleViewController` property at any time to see what controller to present the alert from. Check out [the docs](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UINavigationController_Class/#//apple_ref/occ/instp/UINavigationController/visibleViewController) – Lubo Apr 06 '16 at 13:30
  • @jeet.chanchawat Please do not add code to another user's answer. We don't want to put words in their mouth. If you have a different answer, please add an additional answer. – JAL Sep 12 '16 at 14:14
  • 2
    I did it because I don't want to take credits of someone else's work. It was @ZevEisenberg 's solution which I modified for swift 3.0 . If I would have added another answer then I might have got vote ups which he deserves. – jeet.chanchawat Sep 13 '16 at 18:17
  • 1
    Oh hey, I missed all the drama yesterday, but I happen to have just updated the post for Swift 3. I don't know what SO's policy is on updating old answers for new language versions, but I personally don't mind it, as long as the answer is correct! – Zev Eisenberg Sep 13 '16 at 18:32
  • 1
    Use a delegate instead! – Andrew Kirna May 16 '19 at 13:26
46

I posted a similar question a couple months ago and think I've finally solved the problem. Follow the link at the bottom of my post if you just want to see the code.

The solution is to use an additional UIWindow.

When you want to display your UIAlertController:

  1. Make your window the key and visible window (window.makeKeyAndVisible())
  2. Just use a plain UIViewController instance as the rootViewController of the new window. (window.rootViewController = UIViewController())
  3. Present your UIAlertController on your window's rootViewController

A couple things to note:

  • Your UIWindow must be strongly referenced. If it's not strongly referenced it will never appear (because it is released). I recommend using a property, but I've also had success with an associated object.
  • To ensure that the window appears above everything else (including system UIAlertControllers), I set the windowLevel. (window.windowLevel = UIWindowLevelAlert + 1)

Lastly, I have a completed implementation if you just want to look at that.

https://github.com/dbettermann/DBAlertController

Community
  • 1
  • 1
Dylan Bettermann
  • 753
  • 8
  • 14
  • You don't have this for Objective-C, do you? – SAHM Jun 23 '15 at 23:49
  • 2
    Yes, it even works in Swift 2.0/iOS 9. I'm working on an Objective-C version right now because someone else asked for it (maybe it was you). I'll post back when I'm done. – Dylan Bettermann Jun 24 '15 at 02:49
36

Pretty generic UIAlertController extension for all cases of UINavigationController and/or UITabBarController. Also works if there's a modal VC on screen at the moment.

Usage:

//option 1:
myAlertController.show()
//option 2:
myAlertController.present(animated: true) {
    //completion code...
}

This is the extension:

//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)
          }
        }
    }
}
Gerald
  • 567
  • 1
  • 10
  • 17
Aviel Gross
  • 9,770
  • 3
  • 52
  • 62
  • 1
    I was using this solution, and I found it really perfect, elegant, clean... BUT, recently I had to change my root view controller to a view not in the view hierarchy, so this code became useless. Anyone thinking of a dix for keeping using this? –  Nov 30 '15 at 16:30
  • 1
    I use a combination of this solution with sometinhg else: I have a singleton `UI` class which holds a (weak!) `currentVC` of type `UIViewController`.I have `BaseViewController` which inherits from `UIViewController` and set `UI.currentVC` to `self` on `viewDidAppear` then to `nil` on `viewWillDisappear`. All my view controllers in the app inherit `BaseViewController`. That way if you have something in `UI.currentVC` (it's not `nil`...) - it is definetly not in the middle of a presentation animation, and you can ask it to present your `UIAlertController`. – Aviel Gross Nov 30 '15 at 17:33
  • 1
    As per below, the root view controller might be presenting something with a segue, in which case your last if statement fails, so I had to add `else { if let presentedViewController = controller.presentedViewController { presentedViewController.presentViewController(self, animated: animated, completion: completion) } else { controller.presentViewController(self, animated: animated, completion: completion) } }` – Niklas Apr 08 '16 at 11:26
32

Improving on agilityvision's answer, you'll need to create a window with a transparent root view controller and present the alert view from there.

However as long as you have an action in your alert controller, you don't need to keep a reference to the window. As a final step of the action handler block, you just need to hide the window as part of the cleanup task. By having a reference to the window in the handler block, this creates a temporary circular reference that would be broken once the alert controller is dismissed.

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];
Community
  • 1
  • 1
adib
  • 8,285
  • 6
  • 52
  • 91
26

The following solution did not work even though it looked quite promising with all the versions. This solution is generating WARNING.

Warning: Attempt to present on whose view is not in the window hierarchy!

https://stackoverflow.com/a/34487871/2369867 => This is looked promising then. But it was not in Swift 3. So I am answering this in Swift 3 and this is not template example.

This is rather fully functional code by itself once you paste inside any function.

Quick Swift 3 self-contained code

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)

This is tested and working code in Swift 3.

Community
  • 1
  • 1
mythicalcoder
  • 3,143
  • 1
  • 32
  • 42
  • 1
    This code worked perfectly for me, in a context where a UIAlertController was being fired off in the App Delegate regarding a migration issue, before any root view controller had been loaded. Worked great, no warnings. – Duncan Babbage Apr 06 '17 at 21:52
  • 3
    Just a reminder: you need to store a strong reference to your `UIWindow` or else the window will be released and will disappear shortly after going out of scope. – Allison Jun 20 '19 at 03:17
25

Here's mythicalcoder's answer as an extension, tested & working in 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)
    }

}

Example usage:

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")
})
Joshua
  • 3,055
  • 3
  • 22
  • 37
bobbyrehm
  • 446
  • 4
  • 5
21

This works in Swift for normal view controllers and even if there is a navigation controller on the screen:

let alert = UIAlertController(...)

let alertWindow = UIWindow(frame: UIScreen.main.bounds)
alertWindow.rootViewController = UIViewController()
alertWindow.windowLevel = UIWindowLevelAlert + 1;
alertWindow.makeKeyAndVisible()
alertWindow.rootViewController?.presentViewController(alert, animated: true, completion: nil)
ElOjcar
  • 301
  • 2
  • 4
  • 12
William Entriken
  • 37,208
  • 23
  • 149
  • 195
14

Adding on to Zev's answer (and switching back to Objective-C), you could run into a situation where your root view controller is presenting some other VC via a segue or something else. Calling presentedViewController on the root VC will take care of this:

[[UIApplication sharedApplication].keyWindow.rootViewController.presentedViewController presentViewController:alertController animated:YES completion:^{}];

This straightened out an issue I had where the root VC had segued to another VC, and instead of presenting the alert controller, a warning like those reported above was issued:

Warning: Attempt to present <UIAlertController: 0x145bfa30> on <UINavigationController: 0x1458e450> whose view is not in the window hierarchy!

I haven't tested it, but this may also be necessary if your root VC happens to be a navigation controller.

Kevin Sliech
  • 421
  • 6
  • 16
  • Hum I am running into this problem in Swift, and I dont find how to translate your objc code to swift, help would be much appreciated! –  Nov 30 '15 at 16:36
  • 2
    @Mayerz translating Objective-C to Swift shouldn't be such a big deal ;) but here you are: `UIApplication.sharedApplication().keyWindow?.rootViewController?.presentedViewController?.presentViewController(controller, animated: true, completion: nil)` – borchero Dec 31 '15 at 01:26
  • Thanks Olivier, you are right, it's easy as pie, and I did translated it this way, but the problem was lying somewhere else. Thanks anyway! –  Jan 04 '16 at 09:50
  • `Attempting to load the view of a view controller while it is deallocating is not allowed and may result in undefined behavior ()` – Mojo66 Apr 19 '16 at 22:58
  • 2
    I went with the same approach, use the `rootViewController.presentedViewController` if its not nil, otherwise using `rootViewController`. For a fully generic solution, it may be necessary to walk the chain of `presentedViewController`s to get at the `topmost` VC – Protongun Jul 20 '16 at 02:48
12

Swift 5

It's important to hide the window after showing the message.

func showErrorMessage(_ message: String) {
    let alertWindow = UIWindow(frame: UIScreen.main.bounds)
    alertWindow.rootViewController = UIViewController()

    let alertController = UIAlertController(title: "Error", message: message, preferredStyle: UIAlertController.Style.alert)
    alertController.addAction(UIAlertAction(title: "Close", style: UIAlertAction.Style.cancel, handler: { _ in
        alertWindow.isHidden = true
    }))
    
    alertWindow.windowLevel = UIWindow.Level.alert + 1;
    alertWindow.makeKeyAndVisible()
    alertWindow.rootViewController?.present(alertController, animated: true, completion: nil)
}
exp
  • 410
  • 6
  • 11
  • Will hiding the alertWindow remove the UIViewController you have created? I think it's better to dismiss the rootViewController like this `alertWindow.rootViewController?.dismiss(animated: false)` – torof Mar 13 '21 at 10:17
  • 1
    This is the best one, but in the close handler you should `alertWindow.removeFromSuperview()` not let them keep adding a new UIWindow() every time the root function is called. – Andrew Hodel Feb 06 '22 at 12:34
10

@agilityvision's answer translated to Swift4/iOS11. I haven't used localized strings, but you can change that easily:

import UIKit

/** An alert controller that can be called without a view controller.
 Creates a blank view controller and presents itself over that
 **/
class AlertPlusViewController: UIAlertController {

    private var alertWindow: UIWindow?

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        self.alertWindow?.isHidden = true
        alertWindow = nil
    }

    func show() {
        self.showAnimated(animated: true)
    }

    func showAnimated(animated _: Bool) {

        let blankViewController = UIViewController()
        blankViewController.view.backgroundColor = UIColor.clear

        let window = UIWindow(frame: UIScreen.main.bounds)
        window.rootViewController = blankViewController
        window.backgroundColor = UIColor.clear
        window.windowLevel = UIWindowLevelAlert + 1
        window.makeKeyAndVisible()
        self.alertWindow = window

        blankViewController.present(self, animated: true, completion: nil)
    }

    func presentOkayAlertWithTitle(title: String?, message: String?) {

        let alertController = AlertPlusViewController(title: title, message: message, preferredStyle: .alert)
        let okayAction = UIAlertAction(title: "Ok", style: .default, handler: nil)
        alertController.addAction(okayAction)
        alertController.show()
    }

    func presentOkayAlertWithError(error: NSError?) {
        let title = "Error"
        let message = error?.localizedDescription
        presentOkayAlertWithTitle(title: title, message: message)
    }
}
Dylan Colaco
  • 325
  • 2
  • 11
  • I was getting a black background with the accepted answer. `window.backgroundColor = UIColor.clear` fixed that. `viewController.view.backgroundColor = UIColor.clear` doesn't appear to be necessary. – Ben Patch Mar 23 '18 at 18:25
  • Keep in mind that Apple warns about `UIAlertController` subclassing: ```The UIAlertController class is intended to be used as-is and does not support subclassing. The view hierarchy for this class is private and must not be modified.``` https://developer.apple.com/documentation/uikit/uialertcontroller – Grubas Jun 07 '18 at 18:55
10

For iOS 13, building on the answers by mythicalcoder and bobbyrehm:

In iOS 13, if you are creating your own window to present the alert in, you are required to hold a strong reference to that window or else your alert won't be displayed because the window will be immediately deallocated when its reference exits scope.

Furthermore, you'll need to set the reference to nil again after the alert is dismissed in order to remove the window to continue to allow user interaction on the main window below it.

You can create a UIViewController subclass to encapsulate the window memory management logic:

class WindowAlertPresentationController: UIViewController {

    // MARK: - Properties

    private lazy var window: UIWindow? = UIWindow(frame: UIScreen.main.bounds)
    private let alert: UIAlertController

    // MARK: - Initialization

    init(alert: UIAlertController) {

        self.alert = alert
        super.init(nibName: nil, bundle: nil)
    }

    required init?(coder aDecoder: NSCoder) {

        fatalError("This initializer is not supported")
    }

    // MARK: - Presentation

    func present(animated: Bool, completion: (() -> Void)?) {

        window?.rootViewController = self
        window?.windowLevel = UIWindow.Level.alert + 1
        window?.makeKeyAndVisible()
        present(alert, animated: animated, completion: completion)
    }

    // MARK: - Overrides

    override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {

        super.dismiss(animated: flag) {
            self.window = nil
            completion?()
        }
    }
}

You can use this as is, or if you want a convenience method on your UIAlertController, you can throw it in an extension:

extension UIAlertController {

    func presentInOwnWindow(animated: Bool, completion: (() -> Void)?) {

        let windowAlertPresentationController = WindowAlertPresentationController(alert: self)
        windowAlertPresentationController.present(animated: animated, completion: completion)
    }
}
Logan Gauthier
  • 393
  • 2
  • 6
  • This doesn't work if you need to dismiss the alert manually - the WindowAlertPresentationController is never de-allocated, resulting in a frozen UI - nothing is interactive due to the window still there – JBlake Oct 20 '19 at 18:20
  • 1
    If you want to dismiss the alert manually, make sure to call `dismiss` on the WindowAlertPresentationController directly `alert.presentingViewController?.dismiss(animated: true, completion: nil)` – JBlake Oct 20 '19 at 18:25
  • let alertController = UIAlertController(title: "title", message: "message", preferredStyle: .alert); alertController.presentInOwnWindow(animated: false, completion: nil) works great for me! Thanks! – Brian Bird Nov 22 '19 at 15:59
  • This works on iPhone 6 with iOS 12.4.5, but not on iPhone 11 Pro with iOS 13.3.1. There's no error, but the alert never gets displayed. Any suggestion would be appreciated. – jl303 Feb 09 '20 at 19:03
  • Works great for iOS 13. Does not work in Catalyst -- once the alert is dismissed, the app is not interact-able. See @Peter Lapisu's solution – JBlake Mar 21 '20 at 15:23
7

Create Extension like in Aviel Gross answer. Here You have Objective-C extension.

Here You have header file *.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

And implementation: *.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

You are using this extension in Your implementation file like this:

#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];
Marcin Kapusta
  • 5,076
  • 3
  • 38
  • 55
7

Swift 4+

Solution I use for years with no issues at all. First of all I extend UIWindow to find it's visibleViewController. NOTE: if you using custom collection* classes (such as side menu) you should add handler for this case in following extension. After getting top most view controller it's easy to present UIAlertController just like UIAlertView.

extension UIAlertController {

  func show(animated: Bool = true, completion: (() -> Void)? = nil) {
    if let visibleViewController = UIApplication.shared.keyWindow?.visibleViewController {
      visibleViewController.present(self, animated: animated, completion: completion)
    }
  }

}

extension UIWindow {

  var visibleViewController: UIViewController? {
    guard let rootViewController = rootViewController else {
      return nil
    }
    return visibleViewController(for: rootViewController)
  }

  private func visibleViewController(for controller: UIViewController) -> UIViewController {
    var nextOnStackViewController: UIViewController? = nil
    if let presented = controller.presentedViewController {
      nextOnStackViewController = presented
    } else if let navigationController = controller as? UINavigationController,
      let visible = navigationController.visibleViewController {
      nextOnStackViewController = visible
    } else if let tabBarController = controller as? UITabBarController,
      let visible = (tabBarController.selectedViewController ??
        tabBarController.presentedViewController) {
      nextOnStackViewController = visible
    }

    if let nextOnStackViewController = nextOnStackViewController {
      return visibleViewController(for: nextOnStackViewController)
    } else {
      return controller
    }
  }

}
Timur Bernikovich
  • 5,660
  • 4
  • 45
  • 58
  • I tried everything above, this was literally the only answer that worked! – Rob Oct 08 '22 at 09:52
  • This works great! However the `keyWindow` method is deprecated from iOS 13. [This fixes it.](https://stackoverflow.com/a/74533109/74533109) – Mario Huizinga Nov 23 '22 at 09:13
5

Cross post my answer since these two threads are not flagged as dupes...

Now that UIViewController is part of the responder chain, you can do something like this:

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)
}
Community
  • 1
  • 1
Mark
  • 1,304
  • 12
  • 22
5

If anyone is interested I created a Swift 3 version of @agilityvision answer. The code:

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
    }
}
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
Majster
  • 3,611
  • 5
  • 38
  • 60
5

Zev Eisenberg's answer is simple and straightforward, but it does not always work, and it may fail with this warning message:

Warning: Attempt to present <UIAlertController: 0x7fe6fd951e10>  
 on <ThisViewController: 0x7fe6fb409480> which is already presenting 
 <AnotherViewController: 0x7fe6fd109c00>

This is because the windows rootViewController is not at the top of the presented views. To correct this we need to walk up the presentation chain, as shown in my UIAlertController extension code written in Swift 3:

   /// 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)
}

Updates on 9/15/2017:

Tested and confirmed that the above logic still works great in the newly available iOS 11 GM seed. The top voted method by agilityvision, however, does not: the alert view presented in a newly minted UIWindow is below the keyboard and potentially prevents the user from tapping its buttons. This is because in iOS 11 all windowLevels higher than that of keyboard window is lowered to a level below it.

One artifact of presenting from keyWindow though is the animation of keyboard sliding down when alert is presented, and sliding up again when alert is dismissed. If you want the keyboard to stay there during presentation, you can try to present from the top window itself, as shown in below code:

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

The only not so great part of the above code is that it checks the class name UIRemoteKeyboardWindow to make sure we can include it too. Nevertheless the above code does work great in iOS 9, 10 and 11 GM seed, with the right tint color and without the keyboard sliding artifacts.

CodeBrew
  • 6,457
  • 2
  • 43
  • 48
  • Just went through the many previous answers here and saw Kevin Sliech's answer, which is trying to solve the same issue with a similar approach but which stopped short of walking up the presentation chain, thus making it susceptible to the same error as it tries to solve. – CodeBrew Aug 09 '17 at 14:28
5

Some of these answers only worked partly for me, combining them in the following class method in AppDelegate was the solution for me. It works on iPad, in UITabBarController views, in UINavigationController, en when presenting modals. Tested on iOS 10 and 13.

+ (UIViewController *)rootViewController {
    UIViewController *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;
    while (rootViewController.presentedViewController != nil)
        rootViewController = rootViewController.presentedViewController;
    return rootViewController;
}

Usage:

[[AppDelegate rootViewController] presentViewController ...
Eerko
  • 136
  • 2
  • 6
  • Does UIApplication in your code refer to your app's UIApplication? Does your code not interfere with the Share process? – daniel May 14 '23 at 03:26
  • @daniel UIApplication is the system native Objective-C class. I'm not aware of any issues with the share process. – Eerko May 15 '23 at 07:39
  • I'm trying to use these codes to show an alert in my Share Extension so that the alert shows when an app shares data with my app, and the alert quickly disappears within say 1 minute. I'm posting an answer for that specifically. My cod works but the alert doesn't show, even wen a Timer object is printing to debug window every second. – daniel May 15 '23 at 21:54
  • I've replaced the last if with a while loop to make sure stacked presented view controllers are handled correctly as well. – Eerko Aug 02 '23 at 08:55
4

Shorthand way to do present the alert in Objective-C:

[[[[UIApplication sharedApplication] keyWindow] rootViewController] presentViewController:alertController animated:YES completion:nil];

Where alertController is your UIAlertController object.

NOTE: You'll also need to make sure your helper class extends UIViewController

ViperMav
  • 71
  • 3
4

iOS13 scene support (when using UIWindowScene)

import UIKit

private var windows: [String:UIWindow] = [:]

extension UIWindowScene {
    static var focused: UIWindowScene? {
        return UIApplication.shared.connectedScenes
            .first { $0.activationState == .foregroundActive && $0 is UIWindowScene } as? UIWindowScene
    }
}

class StyledAlertController: UIAlertController {

    var wid: String?

    func present(animated: Bool, completion: (() -> Void)?) {

        //let window = UIWindow(frame: UIScreen.main.bounds)
        guard let window = UIWindowScene.focused.map(UIWindow.init(windowScene:)) else {
            return
        }
        window.rootViewController = UIViewController()
        window.windowLevel = .alert + 1
        window.makeKeyAndVisible()
        window.rootViewController!.present(self, animated: animated, completion: completion)

        wid = UUID().uuidString
        windows[wid!] = window
    }

    open override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        if let wid = wid {
            windows[wid] = nil
        }

    }

}
Peter Lapisu
  • 19,915
  • 16
  • 123
  • 179
  • 1
    UIAlerController shouldn't be subclassed according to documentation https://developer.apple.com/documentation/uikit/uialertcontroller – qwerty Jun 24 '20 at 08:12
  • UIButton shouldn't also be sub-classed and everybody does... as long as you don't modify private methods and don't change the views inside the alertcontroller, its ok – Peter Lapisu Jun 26 '20 at 07:29
  • 2
    I couldn't find any reference to `UIButton shouldn't also be sub-classed`. Can you please share the source? – qwerty Jun 28 '20 at 15:40
  • Thank you! Having UIScenes really messed with how to create the window, and your approach to that is nice. – Carlos Fonseca Oct 22 '20 at 15:35
4

Updated to work with iOS 13 Scenes which breaks the new UIWindow approach. Swift 5.1.

fileprivate var alertWindows = [UIAlertController:UIWindow]()

extension UIAlertController {

    func presentInNewWindow(animated: Bool, completion: (() -> Void)?) {
        let foregroundActiveScene = UIApplication.shared.connectedScenes.filter { $0.activationState == .foregroundActive }.first
        guard let foregroundWindowScene = foregroundActiveScene as? UIWindowScene else { return }

        let window = UIWindow(windowScene: foregroundWindowScene)
        alertWindows[self] = window

        window.rootViewController = UIViewController()
        window.windowLevel = .alert + 1
        window.makeKeyAndVisible()
        window.rootViewController!.present( self, animated: animated, completion: completion)
    }

    open override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        alertWindows[self] = nil
    }

}
December
  • 584
  • 5
  • 10
  • 1
    viewDidDisappear() isn't getting called after dismissing the alert. Does anyone know why? The apparent result is that the app is frozen – JorgeZapatero Sep 29 '21 at 21:01
  • @JorgeZapatero Did you figure out why viewDidDisappear() gets called, and is that a problem with a solution? – daniel May 14 '23 at 03:21
3

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!

computingfreak
  • 4,939
  • 1
  • 34
  • 51
Andrew
  • 1,344
  • 1
  • 12
  • 20
3
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
        }
    }
}

With this you can easily present your alert like so

UIApplication.topMostViewController?.present(viewController, animated: true, completion: nil)

One thing to note is that if there's a UIAlertController currently being displayed, UIApplication.topMostViewController will return a UIAlertController. Presenting on top of a UIAlertController has weird behavior and should be avoided. As such, you should either manually check that !(UIApplication.topMostViewController is UIAlertController) before presenting, or add an else if case to return nil if self is UIAlertController

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
        }
    }
}
NSExceptional
  • 1,368
  • 15
  • 12
2

You can send the current view or controller as a parameter:

+ (void)myUtilityMethod:(id)controller {
    // do stuff
    // something bad happened, display an alert.
}
Pablo A.
  • 2,042
  • 1
  • 17
  • 27
  • Yes, that's possible and would work. But for me, it's got a bit of a code smell. Parameters passed should generally be required for the called method to perform it's primary function. Plus all the existing calls would need to be modified. – Murray Sagal Apr 03 '15 at 13:48
2

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

Complete example:

[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.

Community
  • 1
  • 1
Roman B.
  • 3,598
  • 1
  • 25
  • 21
2

I tried everything mentioned, but with no success. The method which I used for Swift 3.0 :

extension UIAlertController {
    func show() {
        present(animated: true, completion: nil)
    }

    func present(animated: Bool, completion: (() -> Void)?) {
        if var topController = UIApplication.shared.keyWindow?.rootViewController {
            while let presentedViewController = topController.presentedViewController {
                topController = presentedViewController
            }
            topController.present(self, animated: animated, completion: completion)
        }
    }
}
AaoIi
  • 8,288
  • 6
  • 45
  • 87
Dragisa Dragisic
  • 731
  • 2
  • 9
  • 19
1

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];
}
wonder.mice
  • 7,227
  • 3
  • 36
  • 39
  • This is actually ingenious. May not be ideal in all scenarios, especially if you're using a child view controller that should ideally not be presenting a view controller, but this can be improved upon. – strangetimes Jul 24 '20 at 14:52
1

Another option:

    var topController:UIViewController = UIApplication.shared.keyWindow!.rootViewController!
    while ((topController.presentedViewController) != nil) {
        topController = topController.presentedViewController!
    }
    topController.present(alert, animated:true, completion:nil)
Mike Koene
  • 330
  • 3
  • 15
1

I know this was for iOS and since nearly all the links in the search engines find iOS posts I thought I would offer this up to the macOS developers.

Swift 5.5 on macOS

I added this to one of my methods in another class based off Darkngs's answer:

let alert = NSAlert()
let viewController = NSApplication.shared.keyWindow?.contentViewController
alert.messageText = "An Alert Message."
alert.addButton(withTitle: "Ok")
alert.beginSheetModal(for: (viewController?.view.window)!) {
    (returnCode: NSApplication.ModalResponse) -> Void in
}
SouthernYankee65
  • 1,129
  • 10
  • 22
0

You can try to implement a category on UIViewController with a method like - (void)presentErrorMessage;, and and inside that method you implement UIAlertController and then present it on self. Then in your client code you will have something like:

[myViewController presentErrorMessage];

In that way you'll avoid unneccessary parameters and warnings about the view not being in window hierarchy.

6EQUJ5
  • 3,142
  • 1
  • 21
  • 29
Vlad Soroka
  • 245
  • 1
  • 13
  • Except that I don't have `myViewController` in the code where the bad thing happens. That's in a utility method which knows nothing about the view controller that called it. – Murray Sagal Apr 07 '15 at 16:34
  • 2
    IMHO presenting any views (thus alerts) to the user is the responsibility of ViewControllers. So if some part of the code knows nothing about viewController it shouldn't present any errors to user but rather pass them to "viewController aware" parts of the code – Vlad Soroka Apr 07 '15 at 18:59
  • 2
    I agree. But the convenience of the now deprecated `UIAlertView` led me to break that rule in a few spots. – Murray Sagal Apr 07 '15 at 19:09
0

There 2 approaches that you can use:

-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 

Usage:

[[UIViewController topViewController] presentViewController:alertController ...];
Gralex
  • 4,285
  • 7
  • 26
  • 47
0

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;
}
Sound Blaster
  • 4,778
  • 1
  • 24
  • 32
0

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);
    }
}
john07
  • 562
  • 6
  • 16
0

@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
  }
}
Justin Wright
  • 163
  • 2
  • 9
0

In Swift 3

let alertLogin = UIAlertController.init(title: "Your Title", message:"Your message", preferredStyle: .alert)
                                    alertLogin.addAction(UIAlertAction(title: "Done", style:.default, handler: { (AlertAction) in

                                    }))
                                    self.window?.rootViewController?.present(alertLogin, animated: true, completion: nil)
Amul4608
  • 1,390
  • 14
  • 30
0

Swift 4+ / iOS 13:

Based on https://stackoverflow.com/a/47797463/7493938 which uses the .keyWindow method which is deprecated from iOS 13.

extension UIAlertController {
  func show(animated: Bool = true, completion: (() -> Void)? = nil) {
    if let visibleViewController = UIApplication.shared.windows.filter({$0.isKeyWindow}).first?.visibleViewController {
      visibleViewController.present(self, animated: animated, completion: completion)
    }
  }
}

(I can not edit the original answer as the edit queue is full.

Mario Huizinga
  • 780
  • 7
  • 14
-2

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.

svlasov
  • 9,923
  • 2
  • 38
  • 39
runios
  • 384
  • 1
  • 2
  • 14
  • Thanks runios. I see that your answer works but `myUtilityMethod` gets called from a lot of view controllers. And the idea with the utility method is to make it easy to call. Adding an observer and handling the notification each time it's needed removes all the convenience. – Murray Sagal Oct 24 '14 at 19:42
-2

Swift 5

I just created a new window and added alert view controller in it.

Check my class TopViewController:

https://gist.github.com/odnaks/3f3fd0d20f318c6276e76d0f9d7de5a7

I use it very simple, like UIAlertController:

 let alert = TopAlertController()
 alert.title = "title"
 alert.message = "message"
 alert.addAction(UIAlertAction(title: "Ок", style: .default, handler: { _ in }))
 alert.show()
Kseniya
  • 97
  • 1
  • 2