Question

I have a Utils class which shows UIAlertView when certain notifications are triggered. Is there a way to dismiss any open UIAlertViews before showing a new one?

Currenty I am doing this when the app enters the background using

[self checkViews:application.windows];

on applicationDidEnterBackground

- (void)checkViews:(NSArray *)subviews {
    Class AVClass = [UIAlertView class];
    Class ASClass = [UIActionSheet class];
    for (UIView * subview in subviews){
        if ([subview isKindOfClass:AVClass]){
            [(UIAlertView *)subview dismissWithClickedButtonIndex:[(UIAlertView *)subview cancelButtonIndex] animated:NO];
        } else if ([subview isKindOfClass:ASClass]){
            [(UIActionSheet *)subview dismissWithClickedButtonIndex:[(UIActionSheet *)subview cancelButtonIndex] animated:NO];
        } else {
            [self checkViews:subview.subviews];
        }
    }
}

This makes it easy on applicationDidEnterBackground as I can use application.windows

Can I use the AppDelegate or anything similar to get all the views, loop through them and dismiss any UIAlertViews?

Was it helpful?

Solution

for (UIWindow* window in [UIApplication sharedApplication].windows) {
  NSArray* subviews = window.subviews;
  if ([subviews count] > 0)
    if ([[subviews objectAtIndex:0] isKindOfClass:[UIAlertView class]])
      [(UIAlertView *)[subviews objectAtIndex:0] dismissWithClickedButtonIndex:[(UIAlertView *)[subviews objectAtIndex:0] cancelButtonIndex] animated:NO];
}

OTHER TIPS

iOS6 compatible version:

for (UIWindow* w in UIApplication.sharedApplication.windows)
    for (NSObject* o in w.subviews)
        if ([o isKindOfClass:UIAlertView.class])
            [(UIAlertView*)o dismissWithClickedButtonIndex:[(UIAlertView*)o cancelButtonIndex] animated:YES];

iOS7 compatible version:

I made a category interface that stores all instance in init method.

I know it's a very inefficient way.

#import <objc/runtime.h>
#import <objc/message.h>

@interface UIAlertView(EnumView)

+ (void)startInstanceMonitor;
+ (void)stopInstanceMonitor;
+ (void)dismissAll;
@end

@implementation UIAlertView(EnumView)
static BOOL _isInstanceMonitorStarted = NO;

+ (NSMutableArray *)instances
{
    static NSMutableArray *array = nil;
    if (array == nil)
        array = [NSMutableArray array];

    return array;
}


- (void)_newInit
{
    [[UIAlertView instances] addObject:[NSValue valueWithNonretainedObject:self]];
    [self _oldInit];
}

- (void)_oldInit
{
    // dummy method for storing original init IMP.
}

- (void)_newDealloc
{
    [[UIAlertView instances] removeObject:[NSValue valueWithNonretainedObject:self]];
    [self _oldDealloc];

}
- (void)_oldDealloc
{
    // dummy method for storing original dealloc IMP.
}

static void replaceMethod(Class c, SEL old, SEL new)
{
    Method newMethod = class_getInstanceMethod(c, new);
    class_replaceMethod(c, old, method_getImplementation(newMethod), method_getTypeEncoding(newMethod));
}

+ (void)startInstanceMonitor
{
    if (!_isInstanceMonitorStarted) {
        _isInstanceMonitorStarted = YES;
        replaceMethod(UIAlertView.class, @selector(_oldInit), @selector(init));
        replaceMethod(UIAlertView.class, @selector(init), @selector(_newInit));

        replaceMethod(UIAlertView.class, @selector(_oldDealloc), NSSelectorFromString(@"dealloc"));
        replaceMethod(UIAlertView.class, NSSelectorFromString(@"dealloc"), @selector(_newDealloc));
    }
}

+ (void)stopInstanceMonitor
{
    if (_isInstanceMonitorStarted) {
        _isInstanceMonitorStarted = NO;
        replaceMethod(UIAlertView.class, @selector(init), @selector(_oldInit));
        replaceMethod(UIAlertView.class, NSSelectorFromString(@"dealloc"), @selector(_oldDealloc));
    }
}

+ (void)dismissAll
{
    for (NSValue *value in [UIAlertView instances]) {
        UIAlertView *view = [value nonretainedObjectValue];

        if ([view isVisible]) {
            [view dismissWithClickedButtonIndex:view.cancelButtonIndex animated:NO];
        }
    }
}
@end

Start instance monitoring before using UIAlertView.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
   //...  
   //...

    [UIAlertView startInstanceMonitor];

    return YES;
}

Call dismissAll before showing another.

[UIAlertView dismissAll];

It's better using a singleton pattern if you can control all UIAlertViews.

But in my case, I need this code for closing javascript alert dialog in a UIWebView.

Since UIAlertView is deprecated in iOS8 in favor of UIAlertController (which is a UIViewController, presented modally), you can't preset 2 alerts at the same time (from the same viewController at least). The second alert will simply not be presented.

I wanted to partially emulate UIAlertView's behavior, as well as prevent showing multiple alerts at once. Bellow is my solution, which uses window's rootViewController for presenting alerts (usually, that is appDelegate's navigation controller). I declared this in AppDelegate, but you can put it where you desire.

If you encounter any sorts of problems using it, please report here in comments.

@interface UIViewController (UIAlertController)

// these are made class methods, just for shorter semantics. In reality, alertControllers
// will be presented by window's rootViewController (appdelegate.navigationController)
+ (UIAlertController *)presentAlertWithTitle:(NSString *)title
                                     message:(NSString *)message
                           cancelButtonTitle:(NSString *)cancelButtonTitle
                           otherButtonTitles:(NSArray *)otherButtonTitles
                                     handler:(void (^)(NSInteger buttonIndex))block;
+ (UIAlertController *)presentAlertWithTitle:(NSString *)title
                                     message:(NSString *)message
                           cancelButtonTitle:(NSString *)cancelButtonTitle;

@end

@implementation UIViewController (UIAlertController)

+ (UIAlertController *)presentAlertWithTitle:(NSString *)title
                                     message:(NSString *)message
                           cancelButtonTitle:(NSString *)cancelButtonTitle
{
    return [self presentAlertWithTitle:title message:message cancelButtonTitle:cancelButtonTitle
                     otherButtonTitles:nil handler:nil];
}
+ (UIAlertController *)presentAlertWithTitle:(NSString *)title
                                     message:(NSString *)message
                           cancelButtonTitle:(NSString *)cancelButtonTitle
                           otherButtonTitles:(NSArray *)otherButtonTitles
                                     handler:(void (^)(NSInteger buttonIndex))block
{
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:title message:message
                                                            preferredStyle:UIAlertControllerStyleAlert];
    UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:cancelButtonTitle style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
        if (block)
            block(0);
    }];
    [alert addAction:cancelAction];
    [otherButtonTitles enumerateObjectsUsingBlock:^(NSString *title, NSUInteger idx, BOOL *stop) {
        UIAlertAction *action = [UIAlertAction actionWithTitle:title style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
            if (block)
                block(idx + 1); // 0 is cancel
        }];
        [alert addAction:action];
    }];

    id<UIApplicationDelegate> appDelegate = [[UIApplication sharedApplication] delegate];
    UIViewController *rootViewController = appDelegate.window.rootViewController;
    if (rootViewController.presentedViewController) {
        [rootViewController dismissViewControllerAnimated:NO completion:^{
            [rootViewController presentViewController:alert animated:YES completion:nil];
        }];
    } else {
        [rootViewController presentViewController:alert animated:YES completion:nil];
    }
    return alert;
}

@end
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top