50

I've just created a Single View Application project with ViewController class. I would like to show a UIAlertController from a function which is located inside my own class.

Here is my class with an alert.

class AlertController: UIViewController {
     func showAlert() { 
         var alert = UIAlertController(title: "abc", message: "def", preferredStyle: .Alert)
         self.presentViewController(alert, animated: true, completion: nil)
     }
}

Here is ViewController which executes the alert.

class ViewController: UIViewController {
   override func viewDidLoad() {
       super.viewDidLoad()  
   }

   @IBAction func showAlertButton(sender: AnyObject) {
       var alert = AlertController()
       alert.showAlert()
   }
}

This is what I get instead of a beautiful alert.

Warning: Attempt to present UIAlertController: 0x797d2d20 on Sprint1.AlertController: 0x797cc500 whose view is not in the window hierarchy!

What should I do?

wtznc
  • 895
  • 2
  • 14
  • 26
  • any reason you are not using self.show()? – Alex Mar 25 '15 at 13:54
  • 2
    instead of self.presentViewController(alert, animated: true, completion: nil) use self.show() – Alex Mar 25 '15 at 14:08
  • "AlertController" does not have a member named 'show'". – wtznc Mar 25 '15 at 14:12
  • if you want it to behave like a normal uialertview you can have it subclass uialertview instead of uiviewcontroller. Is that what you are looking for? – Alex Mar 25 '15 at 14:14
  • UIAlertView is deprecated in iOS 8. – wtznc Mar 25 '15 at 14:16
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/73777/discussion-between-wtznc-and-alex). – wtznc Mar 25 '15 at 14:26
  • The reason for the crash is because the `AlertController`'s instance you're creating is not added to the view hierarchy. So when you call `presentViewController` on your `alert`, it's getting added to a view which does not exist hence the crash. May I ask why you're putting the `UIAlertController` code in a separate class? – Isuru Mar 25 '15 at 14:35

10 Answers10

64

If you're instancing your UIAlertController from a modal controller, you need to do it in viewDidAppear, not in viewDidLoad or you'll get an error.

Here's my code (Swift 4):

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

    let alertController = UIAlertController(title: "Foo", message: "Bar", preferredStyle: .alert)

    alertController.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil))
    present(alertController, animated: true, completion: nil)
}
Skoua
  • 3,373
  • 3
  • 38
  • 51
  • 1
    Thanks, your solution worked... but I wonder why it does not work in viewDidLoad... – ioopl Jan 02 '17 at 09:44
  • @ioopl I guess this is to prevent alerts to be displayed before viewcontroller or while it's animating. – Skoua Jan 11 '17 at 17:47
18

Let's look at your view hierarchy. You have a ViewController. Then you are creating an AlertController, you are not adding it to your hierarchy and you are calling an instance method on it, that attempts to use the AlertController as presenting controller to show just another controller (UIAlertController).

+ ViewController
    + AlertController (not in hierarchy)
        + UIAlertController (cannot be presented from AlertController)

To simplify your code

class ViewController: UIViewController {
   override func viewDidLoad() {
       super.viewDidLoad()  
   }

   @IBAction func showAlertButton(sender: AnyObject) {
       var alert = UIAlertController(title: "abc", message: "def", preferredStyle: .Alert)
       self.presentViewController(alert, animated: true, completion: nil)
   }
}

This will work.

If you need the AlertController for something, you will have to add it to the hierarchy first, e.g. using addChildViewController or using another presentViewController call.

If you want the class to be just a helper for creating alert, it should look like this:

class AlertHelper {
    func showAlert(fromController controller: UIViewController) { 
        var alert = UIAlertController(title: "abc", message: "def", preferredStyle: .Alert)
        controller.presentViewController(alert, animated: true, completion: nil)
    }
}

called as

 var alert = AlertHelper()
 alert.showAlert(fromController: self)
Sulthan
  • 128,090
  • 22
  • 218
  • 270
  • I need `AlertController` because I have to do few more things inside this class. I have tried `self.addChildViewController(alert) self.presentViewController(alert, animated: true, completion: nil)` but it crashes when button is tapped. Reason: 'Application tried to present modally an active controller .' – wtznc Mar 25 '15 at 14:39
  • @wtznc What exactly do you need to do there? Your problem is obviously an architecture one. – Sulthan Mar 25 '15 at 14:42
  • I have to display that alert after delay, depending on answer from backend. – wtznc Mar 25 '15 at 14:43
14

You can use below function to call alert from any where just include these method in AnyClass

class func topMostController() -> UIViewController {
        var topController: UIViewController? = UIApplication.shared.keyWindow?.rootViewController
        while ((topController?.presentedViewController) != nil) {
            topController = topController?.presentedViewController
        }
        return topController!
    }

    class func alert(message:String){
        let alert=UIAlertController(title: "AppName", message: message, preferredStyle: .alert);
        let cancelAction: UIAlertAction = UIAlertAction(title: "OK", style: .cancel) { action -> Void in

        }
        alert.addAction(cancelAction)
        AnyClass.topMostController().present(alert, animated: true, completion: nil);
    }

Then call

AnyClass.alert(message:"Your Message")
Cœur
  • 37,241
  • 25
  • 195
  • 267
Varun Naharia
  • 5,318
  • 10
  • 50
  • 84
5

Write the following 3 lines, all we need to do is this.

Swift 3.0

private func presentViewController(alert: UIAlertController, animated flag: Bool, completion: (() -> Void)?) -> Void {
     UIApplication.shared.keyWindow?.rootViewController?.present(alert, animated: flag, completion: completion)
  }

Swift 2.0

  private func presentViewController(alert: UIAlertController, animated flag: Bool, completion: (() -> Void)?) -> Void {
     UIApplication.sharedApplication().keyWindow?.rootViewController?.presentViewController(alert, animated: flag, completion: completion)
  }
buczek
  • 2,011
  • 7
  • 29
  • 40
Mitsuaki Ishimoto
  • 3,162
  • 2
  • 25
  • 32
  • This worked just fine for me. I implemented it in my ...Utilities class and hey presto! – user462990 Dec 03 '16 at 13:13
  • 5
    With this there are still situations where rootViewControllers view is released (when a modal vc is presented in front of it e.g.) and your alert will not show. You'll get the error 'Warning: Attempt to present on whose view is not in the window hierarchy!' – Dorian Roy Feb 14 '17 at 18:09
2

If you want to create a separate class for displaying alert like this, subclass NSObject not UIViewController.

And pass the ViewControllers reference from which it is initiated, to the showAlert function so that you can present alert view there.

Prajeet Shrestha
  • 7,978
  • 3
  • 34
  • 63
2

Here is the code of an UIAlertController in a Utility.swift class (not a UIViewController) in Swift3, Thanks Mitsuaki!

private func presentViewController(alert: UIAlertController, animated flag: Bool, completion: (() -> Void)?) -> Void {
    UIApplication.shared.keyWindow?.rootViewController?.present(alert, animated: flag, completion: completion)
}
func warningAlert(title: String, message: String ){
    let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.alert)
    alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler:  { (action) -> Void in
    }))        
 //   self.present(alert, animated: true, completion: nil)
    presentViewController(alert: alert, animated: true, completion: nil)
}
user462990
  • 5,472
  • 3
  • 33
  • 35
  • 4
    With this there are still situations where rootViewControllers view is released (when a modal vc is presented in front of it e.g.) and your alert will not show. You'll get the error 'Warning: Attempt to present on whose view is not in the window hierarchy!' – Dorian Roy Feb 14 '17 at 18:11
2
    let alert = UIAlertController(title: "", message: "YOU SUCCESSFULLY\nCREATED A NEW\nALERT CONTROLLER", preferredStyle: .alert)
    func okAlert(alert: UIAlertAction!)
    {
    
    }
    alert.addAction(UIAlertAction(title: "OK", style: .default, handler: okAlert))
    
    let scenes = UIApplication.shared.connectedScenes
    let windowScene = scenes.first as? UIWindowScene
    let window = windowScene?.windows.first
    var rootVC = window?.rootViewController
    
    if var topController = rootVC
    {
        while let presentedViewController = topController.presentedViewController
        {
            topController = presentedViewController
        }
        rootVC = topController
    }
    rootVC?.present(alert, animated: true, completion: nil)
daj mi spokój
  • 248
  • 1
  • 2
  • 8
0

It helped me to stick a slight delay between the viewDidLoad method and firing the alert method:

   [self performSelector:@selector(checkPhotoPermission) withObject:nil afterDelay:0.1f];
Johnny Rockex
  • 4,136
  • 3
  • 35
  • 55
0

This worked for me:

- (UIViewController *)topViewController{
  return [self topViewController:[UIApplication sharedApplication].keyWindow.rootViewController];
}

- (UIViewController *)topViewController:(UIViewController *)rootViewController
{
  if (rootViewController.presentedViewController == nil) {
    return rootViewController;
  }

  if ([rootViewController.presentedViewController isMemberOfClass:[UINavigationController class]]) {
    UINavigationController *navigationController = (UINavigationController *)rootViewController.presentedViewController;
    UIViewController *lastViewController = [[navigationController viewControllers] lastObject];
    return [self topViewController:lastViewController];
  }

  UIViewController *presentedViewController = (UIViewController *)rootViewController.presentedViewController;
  return [self topViewController:presentedViewController];
}

Implementation:

UIViewController * topViewController = [self topViewController];

Using with alert:

[topViewController presentViewController:yourAlert animated:YES completion:nil];

You can send an alert from any class in your app (that uses UIKit: #import <UIKit/UIKit.h> )

Source here.

Pedro Trujillo
  • 1,559
  • 18
  • 19
0
// I always find it helpful when you want to alert from anywhere it's codebase 
// if you find the error above mentioned in the question' title.

let controller = UIAlertController(title: "", message: "Alert!", preferredStyle: UIAlertController.Style.alert)
    
let action = UIAlertAction(title: "Cancel"   , style: UIAlertAction.Style.cancel, handler: nil)
controller.addAction(action)

// Find Root View Controller
var rootVC = UIApplication.shared.windows.first?.rootViewController

if var topController = rootVC {
    while let presentedViewController = topController.presentedViewController {
        topController = presentedViewController
    }
    rootVC = topController
}
rootVC?.present(controller, animated: true, completion: nil)
Ravi Kumar
  • 1,356
  • 14
  • 22