Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Concurrent UIAlertControllers

I'm porting my app to iOS 8.0 and notice that UIAlertView is deprecated.

So I've changed things to use a UIAlertController. Which works in most circumstances.

Except, when my app opens, it does several checks to report various states back to the user...

E.g... "Warning, you haven't set X up and need to do Y before completing projects" and "Warning, you are using a beta version and do not rely on results" etc...(these are just examples!)

Under the UIAlertView, I would (say) get two alert boxes concurrently which the user has to tap twice to dismiss both...but they both appear.

Under UIAlertController with the code below to present a 'general' alert, I only get one alert message along with a console message:

Warning: Attempt to present UIAlertController: 0x13f667bb0 on TestViewController: 0x13f63cb40 which is already presenting UIAlertController: 0x13f54edf0

So, although the above probably isn't a good example, I'm thinking there may be times when more than one global alert may need to be presented due to 'events' whilst operating an app. Under the old UIAlertView, they would appear but it seems they will not under a UIAlertController.

Can anyone suggest how this could be achieved with a UIAlertController?

Thanks

+(void)presentAlert:(NSString*)alertMessage withTitle:(NSString*)title
{
    UIAlertController *alertView = [UIAlertController
                                    alertControllerWithTitle:title
                                    message:alertMessage
                                    preferredStyle:UIAlertControllerStyleAlert];

    UIAlertAction* ok = [UIAlertAction
                         actionWithTitle:kOkButtonTitle
                         style:UIAlertActionStyleDefault
                         handler:^(UIAlertAction * action)
                         {
                             //Do some thing here
                             [alertView dismissViewControllerAnimated:YES completion:nil];
                         }];

    [alertView addAction:ok];

    UIViewController *rootViewController = [[[UIApplication sharedApplication] delegate] window].rootViewController;
    [rootViewController presentViewController:alertView animated:YES completion:nil];

Edit: I notice that on iOS8, presenting two AlertViews consecutively, they are 'queued' and appear sequentially whereas in iOS7, they appear concurrently. It seems Apple have altered UIAlertView to queue multiple instances. Is there a way to do this with UIAlertController without continuing to use the (deprecated but modified) UIAlertView???

like image 883
Fittoburst Avatar asked Sep 19 '14 11:09

Fittoburst


People also ask

What is UIAlertController in Swift?

An object that displays an alert message.

What is UIAlertController alerts?

Alerts are one of the essential components of an iOS app. We use UIAlertController to get the feedback, confirmation, choose between the options, and show the warning message to the application users. There are the following components of an alert view in iOS.

How do you dismiss an alert with click on outside of the alert iOS?

Step 1 − Open Xcode and create a single view application and name it UIAlertSample. So basically, when we tap on the button an alert will be displayed, when the user taps outside the alert the alert will be dismissing.


2 Answers

I am also facing some problemls with UIAlertController when it comes to present it. Right now the only solution I can suggest is to present alert controller from top most presentedViewContrller if any or window's rootViewController.

UIViewController *presentingViewController = [[[UIApplication sharedApplication] delegate] window].rootViewController;

while(presentingViewController.presentedViewController != nil)
{
    presentingViewController = presentingViewController.presentedViewController;
}

[presentingViewController presentViewController:alertView animated:YES completion:nil];

The warning you are getting is not just limited to UIAlertController. A view controller(window's rootViewController in your case) can present only one view controller at a time.

like image 189
Rahul Wakade Avatar answered Sep 27 '22 20:09

Rahul Wakade


I fully understand the issue here and came up with the following solution via a category of UIAlertController. It's designed so that if an alert is already being presented, it delays showing of the next alert until it receives a notification that the first has been dismissed.

UIAlertController+MH.h

#import <UIKit/UIKit.h>

@interface UIAlertController (MH)

// Gives previous behavior of UIAlertView in that alerts are queued up.
-(void)mh_show;

@end

UIAlertController+MH.m

@implementation UIAlertController (MH)

// replace the implementation of viewDidDisappear via swizzling.
+ (void)load {
    static dispatch_once_t once_token;
    dispatch_once(&once_token,  ^{
        Method originalMethod = class_getInstanceMethod(self, @selector(viewDidDisappear:));
        Method extendedMethod = class_getInstanceMethod(self, @selector(mh_viewDidDisappear:));
        method_exchangeImplementations(originalMethod, extendedMethod);
    });
}

-(UIWindow*)mh_alertWindow{
    return objc_getAssociatedObject(self, "mh_alertWindow");
}

-(void)mh_setAlertWindow:(UIWindow*)window{
    objc_setAssociatedObject(self, "mh_alertWindow", window, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

-(void)mh_show{
    void (^showAlert)() = ^void() {
        UIWindow* w = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
        // we need to retain the window so it can be set to hidden before it is dealloced so the observation fires.
        [self mh_setAlertWindow:w];
        w.rootViewController = [[UIViewController alloc] init];
        w.windowLevel = UIWindowLevelAlert;
        [w makeKeyAndVisible];
        [w.rootViewController presentViewController:self animated:YES completion:nil];
    };

    // check if existing key window is an alert already being shown. It could be our window or a UIAlertView's window.
    UIWindow* keyWindow = [UIApplication sharedApplication].keyWindow;
    if(keyWindow.windowLevel == UIWindowLevelAlert){
        // if it is, then delay showing this new alert until the previous has been dismissed.
        __block id observer;
        observer = [[NSNotificationCenter defaultCenter] addObserverForName:UIWindowDidBecomeHiddenNotification object:keyWindow queue:nil usingBlock:^(NSNotification * _Nonnull note) {
            [[NSNotificationCenter defaultCenter] removeObserver:observer];
            showAlert();
        }];
    }else{
        // otherwise show the alert immediately.
        showAlert();
    }
}

- (void)mh_viewDidDisappear:(BOOL)animated {
    [self mh_viewDidDisappear:animated]; // calls the original implementation
    [self mh_alertWindow].hidden = YES;
}

@end

This code even handles the case where a previous alert was presented via the deprecated UIAlertView, i.e. it waits on it to finish too.

To test this out all you need to do is call show twice in a row with two different alert controllers and you will see the second one waits until the first has been dismissed before being presented.

like image 31
malhal Avatar answered Sep 27 '22 20:09

malhal