10

Is there a way to dismiss all UIAlertControllers that are currently presented?

This is specifically because from anywhere and any state of my app, I need to get to a certain ViewController when a push notification is pressed.

David
  • 7,028
  • 10
  • 48
  • 95
  • What do you mean by _all_ UIAlertControllers? All the UIAlertControllers presented? – Cesare Feb 20 '15 at 19:24
  • @CeceXX Any UIAlertController that is presented. Edited title and text to specify – David Feb 20 '15 at 19:25
  • So you want not to present an UIAlertController or you want to dismiss those who are presented? – Cesare Feb 20 '15 at 19:26
  • I want to dismiss the UIAlertControllers that are currently presented. – David Feb 20 '15 at 19:27
  • checkout my [Github](https://github.com/AgarwalMilan/MAAlertPresenter) project for UIAlertController where I have centralised the display of UIAlertController using a queue and you can easily modify the code to post notification from there – Milan Agarwal Aug 29 '16 at 05:27

6 Answers6

20
func dismissAnyAlertControllerIfPresent() {
    guard let window :UIWindow = UIApplication.shared.keyWindow , var topVC = window.rootViewController?.presentedViewController else {return}
    while topVC.presentedViewController != nil  {
        topVC = topVC.presentedViewController!
    }
    if topVC.isKind(of: UIAlertController.self) {
        topVC.dismiss(animated: false, completion: nil)
    }
}

This worked for me!

Edit: for iOS 13+

 func dismissAnyAlertControllerIfPresent() {
       guard let window = windows.first(where: { $0.isKeyWindow }),
       var topVC = window.rootViewController?.presentedViewController else {return}
       while topVC.presentedViewController != nil  {
           topVC = topVC.presentedViewController!
       }
       if topVC.isKind(of: UIAlertController.self) {
           topVC.dismiss(animated: false, completion: nil)
       }
   }
Mussa Charles
  • 4,014
  • 2
  • 29
  • 24
Agam Mahajan
  • 221
  • 2
  • 5
4

You could subclass your UIAlertControllers, attach NSNotification observers to each which would trigger a method within the UIAlertController subclass to dismiss the alert controller, then post an NSNotification whenever you're ready to dismiss, ex:

class ViewController: UIViewController {
    func presentAlert() {
        // Create alert using AlertController subclass
        let alert = AlertController(title: nil, message: "Message.", preferredStyle: UIAlertControllerStyle.Alert)
        // Add observer to the alert
        NSNotificationCenter.defaultCenter().addObserver(alert, selector: Selector("hideAlertController"), name: "DismissAllAlertsNotification", object: nil)
        // Present the alert
        self.presentViewController(alert, animated: true, completion:nil)
    }
}

// AlertController subclass with method to dismiss alert controller
class AlertController: UIAlertController {
    func hideAlertController() {
        self.dismissViewControllerAnimated(true, completion: nil)
    }
}

Then post the notification whenever you're ready to dismiss the alert (in this case, when the push notification is pressed):

NSNotificationCenter.defaultCenter().postNotificationName("DismissAllAlertsNotification", object: nil)
Lyndsey Scott
  • 37,080
  • 10
  • 92
  • 128
  • @Vov4yk I tested this code before posting it and it worked for me. – Lyndsey Scott Mar 12 '15 at 15:57
  • @Vov4yk But also note that I never modified anything of the UIAlertController within that subclass... I just added a new method, i.e. hideAlertController. – Lyndsey Scott Mar 12 '15 at 16:01
  • did you ever submit such solution to appStore? I' afraid for rejection because subclassing – Vov4yk Mar 12 '15 at 16:14
  • @Vov4yk The docs state: "The view hierarchy for this class is private and must not be modified." But my solution doesn't modify the view hierarchy so I don't see why app review would have an issue. The docs also state, The "UIAlertController class is intended to be used as-is and does not support subclassing." But since my solution works, UIAlertController clearly supports this sort of subclassing that doesn't actually change the structure of the view. – Lyndsey Scott Mar 12 '15 at 16:21
  • checkout my [Github](https://github.com/AgarwalMilan/MAAlertPresenter) project for UIAlertController where I have centralised the display of UIAlertController using a queue and you can easily modify the code to post notification from there. – Milan Agarwal Aug 29 '16 at 05:27
3

You can dismiss an UIAlertController that is currently being presented to the user this way:

self.dismissViewControllerAnimated(true, completion: nil)
Cesare
  • 9,139
  • 16
  • 78
  • 130
2

I have written more generic code in swift 4, This class shows alerts using utility class.

import UIKit

let APP_ORANGE_COLOR = UIColor(red: 1.000, green: 0.412, blue: 0.000, alpha: 1.00)

extension UIAlertController {
    @objc func hideAlertController() {
        self.dismiss(animated: false, completion: nil)
    }
}

class AlertUtility: UIViewController {

    static func showAlert(title: String!, message : String!, viewController: UIViewController) {
        let alert = UIAlertController(title: title, message: message ,preferredStyle: UIAlertControllerStyle.alert)

        alert.addAction(UIAlertAction(title: NSLocalizedString("ok", comment: "ok"), style: UIAlertActionStyle.cancel, handler: nil))
        alert.view.tintColor = APP_ORANGE_COLOR

        NotificationCenter.default.addObserver(alert, selector: #selector(alert.hideAlertController), name: DismissAllAlertsNotification, object: nil)

        viewController.present(alert, animated: true, completion: nil)
    }

    static func showAlertAutoDismiss(title: String!, message : String!) -> Void {
        //let appDelegate = UIApplication.shared.delegate as! AppDelegate

        // the alert view
        let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)

        let topWindow = UIWindow(frame: UIScreen.main.bounds)
        topWindow.rootViewController = UIViewController()
        topWindow.windowLevel = UIWindowLevelAlert + 0.8

        topWindow.makeKeyAndVisible()
        topWindow.rootViewController?.present(alert, animated: true, completion: {})

        // change to desired number of seconds (in this case 5 seconds)
        let when = DispatchTime.now() + 1
        DispatchQueue.main.asyncAfter(deadline: when){
            // your code with delay
            alert.dismiss(animated: true, completion: nil)
            topWindow.isHidden = true
        }
    }

    static func showAlert(title: String!, message : String!) -> Void {
        //let appDelegate = UIApplication.shared.delegate as! AppDelegate

        // the alert view
        let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)

        let topWindow = UIWindow(frame: UIScreen.main.bounds)
        topWindow.rootViewController = UIViewController()
        topWindow.windowLevel = UIWindowLevelAlert + 1
        topWindow.makeKeyAndVisible()
        topWindow.rootViewController?.present(alert, animated: true, completion: {})

        alert.addAction(UIAlertAction(title: NSLocalizedString("ok", comment: "ok"), style: UIAlertActionStyle.cancel, handler: {(_ action: UIAlertAction) -> Void in
            // continue your work
            // important to hide the window after work completed.
            // this also keeps a reference to the window until the action is invoked.
            topWindow.isHidden = true
        }))

        NotificationCenter.default.addObserver(alert, selector: #selector(alert.hideAlertController), name: DismissAllAlertsNotification, object: nil)
        alert.view.tintColor = APP_ORANGE_COLOR
    }


    static func showGenericErrorMessageAlert(viewController: UIViewController) {
        let alert = UIAlertController(title: NSLocalizedString("error", comment: ""), message: NSLocalizedString("generic.error.message", comment: "") ,preferredStyle: UIAlertControllerStyle.alert)

        alert.addAction(UIAlertAction(title: NSLocalizedString("ok", comment: "ok"), style: UIAlertActionStyle.cancel, handler: nil))
        alert.view.tintColor = APP_ORANGE_COLOR
        viewController.present(alert, animated: true, completion: nil)
        NotificationCenter.default.addObserver(alert, selector: #selector(alert.hideAlertController), name: DismissAllAlertsNotification, object: nil)
    }

    static func showComingSoonAlert(viewController: UIViewController) {

        // the alert view
        let alert = UIAlertController(title: "", message: NSLocalizedString("coming.soon", comment: ""), preferredStyle: .alert)

        viewController.present(alert, animated: true, completion: {})

        // change to desired number of seconds (in this case 5 seconds)
        let when = DispatchTime.now() + 1
        DispatchQueue.main.asyncAfter(deadline: when){
            // your code with delay
            alert.dismiss(animated: true, completion: nil)
        }
        NotificationCenter.default.addObserver(alert, selector: #selector(alert.hideAlertController), name: DismissAllAlertsNotification, object: nil)
    }

    // Show alert view with call back
    static func showAlertWithCB(title: String, message: String, isConditional: Bool, viewController: UIViewController, completionBlock: @escaping (_: Bool) -> Void) {

        let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.alert)
        alert.view.tintColor = APP_ORANGE_COLOR
        // Check whether it's conditional or not ('YES' 'NO, or just 'OK')
        if isConditional
        {
            alert.addAction(UIAlertAction(title: NSLocalizedString("cancel", comment: ""), style: UIAlertActionStyle.cancel, handler: { (action: UIAlertAction) in
                alert.dismiss(animated: true, completion: nil)
                completionBlock(false)
            }))

            alert.addAction(UIAlertAction(title: NSLocalizedString("yes", comment: ""), style: UIAlertActionStyle.default, handler: { (action: UIAlertAction) in
                alert.dismiss(animated: true, completion: nil)
                completionBlock(true)
            }))

        }
        else
        {
            alert.addAction(UIAlertAction(title: NSLocalizedString("ok", comment: "ok"), style: UIAlertActionStyle.default, handler: { (action: UIAlertAction) in
                alert.dismiss(animated: true, completion: nil)
                completionBlock(true)
            }))
        }

        NotificationCenter.default.addObserver(alert, selector: #selector(alert.hideAlertController), name: DismissAllAlertsNotification, object: nil)
        viewController.present(alert, animated: true, completion: nil)
    }

    static func showAlertWithTextField(viewController : UIViewController,completionBlock: @escaping (_: Bool, String) -> Void) {

        //1. Create the alert controller.
        let alert = UIAlertController(title: "Report Event?", message: "", preferredStyle: .alert)
        alert.view.tintColor = APP_ORANGE_COLOR
        //2. Add the text field. You can configure it however you need.
        //AlertUtility.addte
        alert.addTextField { (textField) in

            let heightConstraint = NSLayoutConstraint(item: textField, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 50)
            textField.addConstraint(heightConstraint)
            textField.placeholder = "Enter report reason here"
            textField.tintColor = APP_ORANGE_COLOR
            textField.autocapitalizationType = .sentences
        }

        // 3. Grab the value from the text field, and print it when the user clicks OK.
        alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { [weak alert] (_) in
            // Force unwrapping because we know it exists.
            completionBlock(true,"")
            //print("Text field: \(textField.text)")
        }))

        // 3. Grab the value from the text field, and print it when the user clicks OK.
        alert.addAction(UIAlertAction(title: "Submit", style: .default, handler: { [weak alert] (_) in
            let textField = alert?.textFields![0] // Force unwrapping because we know it exists.
            completionBlock(true,(textField?.text)!)
            //print("Text field: \(textField.text)")
        }))

        // 4. Present the alert.
        viewController.present(alert, animated: true, completion: nil)

        let textField = alert.textFields![0]
        let v = UIView.init(frame: textField.frame)
        textField.addSubview(v)
        v.frame = textField.frame
        v.bounds = textField.bounds
        v.backgroundColor = APP_ORANGE_COLOR
        v.superview?.bringSubview(toFront: v)
    }

}

Use it in this way

//sample code - use in your view controller
AlertUtility.showAlertWithCB(title: NSLocalizedString("alert", comment: "") , message: (error)!, isConditional: false, viewController: self, completionBlock: { (yes) in

                      //Your actions on callback
                        self.popToPreviousController()
                    })
AlertUtility.showAlert(title: ALERT_TITLE, message: message, viewController: self)

Post notification when you want/need to auto dismiss alerts in app

let DismissAllAlertsNotification = Notification.Name("DismissAllAlertsNotification")

NotificationCenter.default.post(name: DismissAllAlertsNotification, object: nil)
Pramod More
  • 1,220
  • 2
  • 22
  • 51
  • This was a very useful code while implementing auto logout functionality. App automatically redirects to the login screen but alerts remain on screen. – Pramod More Mar 16 '18 at 12:24
1

They are modal: there will only be one of them at any time and it will have full focus.

It is your responsibility to create your alerts and it is the user that should be dismissing them. You need to make (or use) a custom view to display a stack of messages if you need further control.

davbryn
  • 7,156
  • 2
  • 24
  • 47
1

I used the following extension to achieve this, I hope it may help you

First; you will have an extension for UIApplication to retrieve the RootViewController

extension UIApplication {

   static func topViewControllerInNavigationStack(controller: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
       if let navigationController = controller as? UINavigationController {
            return topViewControllerInNavigationStack(controller: navigationController.visibleViewController)
       }
       if let tabController = controller as? UITabBarController {
           if let selected = tabController.selectedViewController {
             return topViewControllerInNavigationStack(controller: selected)
           }
       }
       if let presented = controller?.presentedViewController {
           return topViewControllerInNavigationStack(controller: presented)
       }
       return controller
   }
}

Second; extension for UIAlertViewController

extension UIAlertController {
     static func dismissPresentedAlertViewController() {
         let viewController = UIApplication.topViewControllerInNavigationStack()
         guard let isKindOf = viewController?.isKind(of: 
         UIAlertController.classForCoder()), isKindOf else {
             return
         }
         viewController?.dismiss(animated: false, completion: nil)
    }
}
Amr Angry
  • 3,711
  • 1
  • 45
  • 37