防止多个UIAlertView重叠弹出

作者: 踩坑小分队 | 来源:发表于2016-10-14 12:04 被阅读2901次

    项目中可能会遇到这种情况,好几个alertView因为逻辑关系全部弹出,用户需要一个个的点击才能将所有的alertView取消掉。或者说这种情况下,我们只需要弹出一个alertView就OK了。

    alertView是怎么弹出的?网上查找资料说是,每次执行[alertView show],这个方法的时候是新建了一个window,将alertView显示在了window上面。代码验证的确是这样的。

    代码验证alertView是添加到哪里的。

    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view, typically from a nib.
        
        UIButton *tempBtn = [UIButton buttonWithType:UIButtonTypeSystem];
        tempBtn.frame = CGRectMake(100, 100, 100, 100);
        tempBtn.backgroundColor = [UIColor cyanColor];
        [tempBtn addTarget:self action:@selector(clickBtn:) forControlEvents:UIControlEventTouchUpInside];
        [self.view addSubview:tempBtn];
        
    }
    
    - (void)clickBtn:(UIButton *)sender
    {
        UIAlertView *alert1 = [[UIAlertView alloc] initWithTitle:@"title1" message:@"message1" delegate:nil cancelButtonTitle:@"取消" otherButtonTitles:@"确定", nil];
        [alert1 show];
        
     NSLog(@"alert1.window = %@   alert1.window.windowLevel = %f",alert1.window,alert1.window.windowLevel);
        NSLog(@"app.window = %@",app.window);
        NSLog(@"windows == %@",[UIApplication sharedApplication].windows);
    }
    

    测试结果:

    alert1.window = <_UIAlertControllerShimPresenterWindow: 0x7f9ee8c07940; frame = (0 0; 414 736); opaque = NO; gestureRecognizers = <NSArray: 0x618000056aa0>; layer = <UIWindowLayer: 0x6180000240a0>>   alert1.window.windowLevel = 2001.000000
    app.window = <UIWindow: 0x7f9ee8f03f80; frame = (0 0; 414 736); autoresize = W+H; gestureRecognizers = <NSArray: 0x608000052f60>; layer = <UIWindowLayer: 0x608000022100>>
     windows == (
        "<UIWindow: 0x7f9ee8f03f80; frame = (0 0; 414 736); autoresize = W+H; gestureRecognizers = <NSArray: 0x608000052f60>; layer = <UIWindowLayer: 0x608000022100>>"
    )
    

    通过打印的结果可以看出:
    1、alert1.window没有在[UIApplication sharedApplication].windows中出现<window和windows的关系参考:http://www.jianshu.com/p/75befce85623>,windows中只有app.window也就是当前的最底层的控件。
    2、alert1.window的windowLevel是2001比app.window的大,APP.window的windowLevel是0,所以alertView显示在了app.window的上面。相关windowLevel的问题参考:http://www.jianshu.com/p/f60471a7d935

    搞懂了alertView显示的大致原理了,那么往我们的需求上靠

    - (void)clickBtn:(UIButton *)sender
    {
        UIAlertView *alert1 = [[UIAlertView alloc] initWithTitle:@"title1" message:@"message1" delegate:nil cancelButtonTitle:@"取消" otherButtonTitles:@"确定", nil];
        [alert1 show];
        
        UIAlertView *alert2 = [[UIAlertView alloc] initWithTitle:@"title2" message:@"message2" delegate:nil cancelButtonTitle:@"取消" otherButtonTitles:@"确定", nil];
        [alert2 show];
        
        
    //    AppDelegate *app = (AppDelegate *)[UIApplication sharedApplication].delegate;
    //    
    //    NSLog(@"alert1.window = %@   alert1.window.windowLevel = %f",alert1.window,alert1.window.windowLevel);
    //    NSLog(@"app.window = %@",app.window);
    //    NSLog(@"windows == %@",[UIApplication sharedApplication].windows);
    }
    

    想要多余的alertView不显示,两种方法:
    1、要么让他不展示
    2、要么让他展示了自己再消失
    第一种我感觉做不到,显示的逻辑是写死的。
    那就拿第二种下手,前提是怎么获取到已经展示的alertView?

    上面介绍的alertView显示,是显示在系统给自己创建的Window上面的,但是这个window还获取不到。那怎么办。
    有这么一种思路,将所有显示的alertView记录在自己的一个数组中,然后不就想干嘛就干嘛了嘛!!关键点是记录的时机,这里选取show方法执行的时候
    思路1:
    使用runtime方法检测show方法,然后在执行show方法的时候记录alertView,相关代码如下:
    创建记录alertView的单例

    #import <Foundation/Foundation.h>
    
    @interface AlertViewRecorder : NSObject
    
    @property (nonatomic, strong)NSMutableArray * alertViewArray;
    
    + (AlertViewRecorder *)shareAlertViewRecorder;
    
    @end
    
    #import "AlertViewRecorder.h"
    
    @implementation AlertViewRecorder
    // 创建单例,记录alertView
    + (AlertViewRecorder *)shareAlertViewRecorder
    {
        static AlertViewRecorder *recoder = nil;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            if(recoder == nil){
                recoder = [[AlertViewRecorder alloc] init];
                
            }
        });
        return recoder;
    }
    
    - (instancetype)init
    {
        self = [super init];
        if (self) {
            self.alertViewArray = [[NSMutableArray alloc] init];
        }
        return self;
    }
    
    @end
    

    关键代码

    #import "UIAlertView+MyAlertView.h"
    #import <objc/message.h>
    #import "AppDelegate.h"
    #import "AlertViewRecorder.h"
    
    @implementation UIAlertView (MyAlertView)
    
    + (void)load
    {
        // 获取将要交换的两个方法
        Method showMethod = class_getInstanceMethod(self, @selector(show));
        Method myShowMethod = class_getInstanceMethod(self, @selector(myShow));
        // 将两个方法互换
        method_exchangeImplementations(showMethod, myShowMethod);
        
    }
    
    - (void)myShow
    {
        // 将之前所有的alertView取出来消失掉
        NSMutableArray *array =  [AlertViewRecorder shareAlertViewRecorder].alertViewArray;
        for (UIAlertView *alertView in array) {
            if ([alertView isKindOfClass:[UIAlertView class]]) {
                [alertView dismissWithClickedButtonIndex:-1 animated:YES];
            }
        }
        
        [array removeAllObjects];
        // 调用自身的方法
        [self myShow];
        [array addObject:self];
    }
    
    @end
    

    测试代码可行;

    思路2:
    创建分类,重写show方法,在重写的show方法中调用show方法的同时,记录alertView到相关数组,和思路1差不多。

    思路1相对于思路2的优点,个人认为,当项目开发了一段时间或者半路接手项目的时候,思路1更有优势。

    如有失误请各位路过大神即时指点,或有更好的做法,也请指点一二,在下感激不尽。
    代码连接:https://github.com/RunOfTheSnail/MyAlertViewDemo

    相关文章

      网友评论

      • Thebloodelves:我们项目是这样的:对于一些不需要用户操作的提示我们不使用alertView而是用MBProgressHUD提示一下,当然用alertView提示的话可以用你的方法隐藏;对于一些需要用户操作的我们用alertView来确定行为比如是否删除好友那么就不能随便隐藏alertView;所以这个文章了解一下alertView的产生还是很有意义的
        踩坑小分队:@Thebloodelves 场景的确不多
        Thebloodelves:@小岩同学 尽量从设计入手吧,隐藏毕竟不寻常
        踩坑小分队:@Thebloodelves 我们的项目中是这样的,如果提示比较重量级,比如其他设备登录,帐号被删除等等,可能会冲突,所以写了一下。刚开始的时候是通过存标识判断的,太麻烦,后期想到了记录方法。

      本文标题:防止多个UIAlertView重叠弹出

      本文链接:https://www.haomeiwen.com/subject/kfiiyttx.html