8

Difference between the legacy UIAlertView and the new UIAlertController is that the latter needs to be presented onto a specific viewcontroller with presentViewController:animated:completion:. This poses an awkward problem for my use case: what if there is already an UIAlertController showing (e.g. a rating dialog) when a second viewcontroller gets presented (e.g. an error dialog due to failed network connection). I have experienced that in this case the second UIAlertController just does not show.

Edit: At the moment I try to show an alert, I do not know if there currently is anything presenting.

How do you cope with this situation?

fabb
  • 11,660
  • 13
  • 67
  • 111
  • [This thread][1] explain correctly how to solve similar issues [1]: http://stackoverflow.com/questions/21179922/can-i-check-if-any-uialertview-displaying-right-now –  Nov 18 '14 at 06:05
  • The solutions in the other thread are fragile and ugly, and probably will break in iOS8. – fabb Nov 23 '14 at 09:31

5 Answers5

5

Since UIAlertController is itself a UIViewController, you can present a second UIAlertController on top of the first one by presenting from the existing one:

alertController.PresentViewController(alertController2,  animated: true, completionHandler: null)
kidshaw
  • 3,423
  • 2
  • 16
  • 28
Bbx
  • 3,184
  • 3
  • 22
  • 33
  • The problem is that one has to find out the top presented viewcontroller first. Let's assume I don't know if there are amy alerts or other presented viewcontrollers showing. – fabb Nov 16 '14 at 07:55
  • Use this to get the top ViewController: private UIViewController GetTopPresentedViewController() { UIViewController currentVC = this.viewController; while (true) { UIViewController nextVC = currentVC.PresentedViewController; if (nextVC == null) { return currentVC; } currentVC = nextVC; } } – Bbx Nov 17 '14 at 11:03
  • see my answer - do not forget the `isBeingDismissed` flag. – fabb Nov 17 '14 at 11:53
5

I found a workaround to find out which viewcontroller I can present the alert upon. I also posted the answer here:

@implementation UIViewController (visibleViewController)

- (UIViewController *)my_visibleViewController {

    if ([self isKindOfClass:[UINavigationController class]]) {
        // do not use method visibleViewController as the presentedViewController could beingDismissed
        return [[(UINavigationController *)self topViewController] my_visibleViewController];
    }

    if ([self isKindOfClass:[UITabBarController class]]) {
        return [[(UITabBarController *)self selectedViewController] my_visibleViewController];
    }

    if (self.presentedViewController == nil || self.presentedViewController.isBeingDismissed) {
        return self;
    }

    return [self.presentedViewController my_visibleViewController];
}

@end

// To show a UIAlertController, present on the following viewcontroller:
UIViewController *visibleViewController = [[UIApplication sharedApplication].delegate.window.rootViewController my_visibleViewController];
Community
  • 1
  • 1
fabb
  • 11,660
  • 13
  • 67
  • 111
0

This code fulfilling the requirement when app has to present some alert on window and before present its checking that if there is any other AlertController presented already, if presented then present the alert on appeared Alertcontroller otherwise present it on window.

Here is one more alternative, you can optimize it according to your requirement.

     func showAlert(message:String) {

        if let alert = self.checkIfAlertViewHasPresented() {
            alert.presentViewController(alertController, animated: true, completion: nil)

        } else {
            self.window?.rootViewController!.presentViewController(alertController, animated: true, completion: nil)
        }
    }

    func checkIfAlertViewHasPresented() -> UIAlertController? {

        if var topController = UIApplication.sharedApplication().keyWindow?.rootViewController {
            while let presentedViewController = topController.presentedViewController {
                topController = presentedViewController
            }
            if topController is UIAlertController {
               return (topController as! UIAlertController)
            } else {
               return nil
            }
        }
        return nil
    }
iDevAmit
  • 1,550
  • 2
  • 21
  • 33
  • This does not cover a few special cases that are covered in the accepted answer. – fabb Oct 12 '16 at 08:41
  • @fabb Agreed with you. I have already stated it before write the code about the condition fulfilled by the chunk of code. I consider my answer as a secondary option if some newbie developer would have problem to understand other complex conditions. – iDevAmit Oct 12 '16 at 08:45
  • IMHO especially newbies should be taught to do things properly, no offense – fabb Oct 12 '16 at 08:47
  • @fabb Agreed with you. I will take care about your suggestions in my future answers and later I will edit my answer with more optimized answer. :) – iDevAmit Oct 12 '16 at 08:52
0

This is what I'm using. this way if alert is already display, I prefer the user to dismissed it and not the app. So, incase the view is already presenting alert, I just wait 5 seconds and try again.

I just want to add, I did not test this too much, but it works.(from 1 test I made), so I hope I'm not missing something, cause I thought about this problem for long time, and this solution sound too easy :)

-(void) alertUserWithTitle:(NSString*) title Message:(NSString*) message
{
    UIAlertController* alert = [UIAlertController alertControllerWithTitle:title                                                                              message:message
                                                                            preferredStyle:UIAlertControllerStyleAlert];

                    UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault
                                                                          handler:^(UIAlertAction * action) {}];

                    [alert addAction:defaultAction];

                    if(self.presentedViewController == nil)
                    {
                        [self presentViewController:alert animated:YES completion:nil];
                    }else
                    {
                        double delayInSeconds = 2.0;
                        dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);

                        dispatch_after(popTime, dispatch_get_main_queue(), ^(void){

                            [self alertUserWithTitle:title Message:message];

                        });

                    }
}
user1105951
  • 2,259
  • 2
  • 34
  • 55
  • Good for some use cases. Not so ideal if the alert should block further user interaction. – fabb Dec 14 '16 at 21:52
0

Here's a solution I use in Swift 3. It is a function that shows an alert to the user, and if you call it multiple times before the user has dismissed the alert, it will add the new alert text to the alert that's already being presented. If some other view is being presented, the alert will not appear. Not all will agree with that behavior, but it works well for simple situations.

extension UIViewController {
    func showAlert(_ msg: String, title: String = "") {
        if let currentAlert = self.presentedViewController as? UIAlertController {
            currentAlert.message = (currentAlert.message ?? "") + "\n\nUpdate:\(title): \(msg)"
            return
        }

        // create the alert
        let alert = UIAlertController(title: title, message: msg, preferredStyle: UIAlertControllerStyle.alert)
        alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil))

        // show the alert
        self.present(alert, animated: true, completion: nil)
    }
}
biomiker
  • 3,108
  • 1
  • 29
  • 26