iOS 更加优雅便捷的UIAlertView/UIAlertController封装使用

evcb1982 8年前
   <p>前言:</p>    <p>这个是将alertView和alertController做了版本适配封装在一起的,提供了变参和数组两种方式,不过现在看来,虽然是“一句话”调用,但并不是很优雅的方式。</p>    <p>这次,改变了方案,将UIAlertView和UIAlertController分开进行了处理,整体代码也轻量了很多。</p>    <ul>     <li>基于UIAlertView封装的JXTAlertView,这个是将之前写Demo时搞的一套快捷使用alertView的工具抽离整理出来的,并提供了C函数直接调用,像这样:<br> jxt_showAlertTitle(@"简易调试使用alert,单按钮,标题默认为“确定”");<br> 就可以直接显示出一个alertView。</li>     <li>基于UIAlertController封装的JXTAlertController,支持iOS8及以上。调用方式为UIViewController的扩展分类方法,支持使用链式语法的方式配置alert的按钮及样式,相对于变参或者数组,更加简洁。</li>    </ul>    <p><strong>JXTAlertManager</strong></p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/c094b10c463fba33b5d2cd59eee4090f.png"></p>    <p style="text-align: center;">JXTAlertManager大体结构</p>    <h2>1. JXTAlertView 便捷调试工具</h2>    <p>之所以叫做快捷调试工具,就是因为这套代码是之前写Demo时搞出来的。所以,如果不是要适配iOS7及以下版本的话,这套代码还是建议只用在平时Demo测试。也因此,并没有针对UIActionSheet再进行封装,说白了是因为懒……</p>    <p>平时写一些Demo代码时,总会用到alert、toast、HUD这些工具,如果没有一套简便的工具,会麻烦很多,所以便从轻量便捷角度出发,基于UIAlertView,封装实现了alert、toast、HUD这些常用工具。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/b4e47990342331987bb15532209d15c2.gif"></p>    <p>JXTAlertView大致效果演示</p>    <h2>1.1.快捷使用alertView</h2>    <p>如果只是简单的一个提示,可以这样使用(这里只是一个示例,详细用法见源码):</p>    <p>jxt_showAlertTitle(@"简易调试使用alert,单按钮,标题默认为“确定”");</p>    <p>其实现是基于:</p>    <pre>  <code class="language-objectivec">[JXTAlertViewshowAlertViewWithTitle:title                              message:message                    cancelButtonTitle:cancelButtonTitle                      otherButtonTitle:otherButtonTitle                    cancelButtonBlock:cancelBlock                      otherButtonBlock:otherBlock];  </code></pre>    <p>这是常用的两个以内的按钮的alertView,也可以这样使用:</p>    <pre>  <code class="language-objectivec">jxt_showAlertTwoButton(@"title", @"message", @"cancel", ^(NSIntegerbuttonIndex) {      NSLog(@"cancel");  }, @"other", ^(NSIntegerbuttonIndex) {      NSLog(@"other");  });  </code></pre>    <p>针对于复杂的多按钮的alertView,还是使用变参方式,按钮响应,根据添加的按钮标题的index号依序区分:</p>    <pre>  <code class="language-objectivec">[JXTAlertViewshowAlertViewWithTitle:@"title"                              message:@"message"                    cancelButtonTitle:@"cancel"                      buttonIndexBlock:^(NSIntegerbuttonIndex) {      if (buttonIndex == 0) {          NSLog(@"cancel");      }      else if (buttonIndex == 1) {          NSLog(@"按钮1");      }      else if (buttonIndex == 2) {          NSLog(@"按钮2");      }      else if (buttonIndex == 3) {          NSLog(@"按钮3");      }      else if (buttonIndex == 4) {          NSLog(@"按钮4");      }      else if (buttonIndex == 5) {          NSLog(@"按钮5");      }  } otherButtonTitles:@"按钮1", @"按钮2", @"按钮3", @"按钮4", @"按钮5", nil];  </code></pre>    <h2>1.2.简单的toast</h2>    <p>这里的toast提示,有别于传统意义上的toast,因为其是基于alertView实现的,是一个没有按钮的alertView。可自定义展示延时时间,支持关闭回调的配置。</p>    <pre>  <code class="language-objectivec">[JXTAlertViewshowToastViewWithTitle:@"title"                              message:@"message"                              duration:2                    dismissCompletion:^(NSIntegerbuttonIndex) {      NSLog(@"关闭");  }];  </code></pre>    <h2>1.3.三种HUD的实现</h2>    <p>这里的HUD区别于toast之处在于,其关闭时机可控,并不是单纯的一个延时展示。</p>    <p>三种HUD是指单纯的文字型、带风火轮(菊花)的加载窗、带进度条的加载窗。</p>    <p>后两者用KVC的方式访问了alertView的私有属性 accessoryView 实现,这样做可能没有太大问题,不过还是不建议线上开发使用,而且利用这种方式访问私有属性本来就是不太安全的,一旦key(私有属性名)改变了,不做容错处理,会崩溃,源码实现中做了一定的容错,但是,一旦对应key变化,也就导致对应功能失效了。</p>    <ul>     <li> <p>示例代码(C函数方式):</p> <pre>  <code class="language-objectivec">jxt_showLoadingHUDTitleMessage(@"title", @"message");  dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{    jxt_dismissHUD();  });  </code></pre> <p>HUD还有对应的简易的显示加载成功失败状态的方法,以及刷新进度条进度值的方法,详情见Demo。</p> </li>    </ul>    <h2>2. JXTAlertController(iOS8)(链式语法的“隐患”)</h2>    <p>JXTAlertController是基于系统的UIAlertController封装的,因此也只能支持iOS8及以上系统版本。</p>    <p>虽然源码中的 JXTAlertManagerHeader.h 做了一个版本适配,但是,其意义更多在于提示,很可能因此出错,所以,如果要适配iOS7,对应方法还是需要自行适配。</p>    <p>下面都以alert举例,actionSheet同理。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/0cffb4392411361f262e39c46d528b7b.gif"></p>    <p style="text-align: center;">JXTAlertController大致效果演示</p>    <h2>2.1.结构说明</h2>    <pre>  <code class="language-objectivec">/**  JXTAlertController: show-alert(iOS8)     @param title             title  @param message           message  @param appearanceProcess alert配置过程  @param actionBlock       alert点击响应回调  */  - (void)jxt_showAlertWithTitle:(nullableNSString *)title                        message:(nullableNSString *)message              appearanceProcess:(JXTAlertAppearanceProcess)appearanceProcess                    actionsBlock:(nullableJXTAlertActionBlock)actionBlockNS_AVAILABLE_IOS(8_0);  </code></pre>    <p>上述方法是针对 UIViewController 做的分类扩展,详见源码。</p>    <p>也就是在某个VC中使用时,可直接用 self 指针调用。</p>    <p>JXTAlertAppearanceProcess 是配置块, JXTAlertActionBlock 是按钮响应回调块。</p>    <h2>2.2.链式语法添加按钮</h2>    <pre>  <code class="language-objectivec">[self jxt_showActionSheetWithTitle:@"title"                            message:@"message"                  appearanceProcess:^(JXTAlertController * _NonnullalertMaker) {      alertMaker.      addActionCancelTitle(@"cancel").      addActionDestructiveTitle(@"按钮1").      addActionDefaultTitle(@"按钮2").      addActionDefaultTitle(@"按钮3").      addActionDestructiveTitle(@"按钮4");  } actionsBlock:^(NSIntegerbuttonIndex, UIAlertAction * _Nonnullaction, JXTAlertController * _NonnullalertSelf) {         if ([action.titleisEqualToString:@"cancel"]) {          NSLog(@"cancel");      }      else if ([action.titleisEqualToString:@"按钮1"]) {          NSLog(@"按钮1");      }      else if ([action.titleisEqualToString:@"按钮2"]) {          NSLog(@"按钮2");      }      else if ([action.titleisEqualToString:@"按钮3"]) {          NSLog(@"按钮3");      }      else if ([action.titleisEqualToString:@"按钮4"]) {          NSLog(@"按钮4");      }  }];  </code></pre>    <p>appearanceProcess 配置块中, alertMaker 是当前alertController对象, addActionCancelTitle(@"cancel") 是添加一个按钮,其等效于:</p>    <pre>  <code class="language-objectivec">[alertControlleraddAction:[UIAlertActionactionWithTitle:@"cancel" style:UIAlertActionStyleCancelhandler:^(UIAlertAction * _Nonnullaction) {     }]];  </code></pre>    <p>这里引入了简单的链式语法,可以连续添加系统支持的三类action按钮,当然 UIAlertActionStyleCancel 这个样式的action只能添加一次。这样可以大大简化代码。</p>    <p>actionsBlock 是action按钮响应回调,可以根据index区分响应,index根据执行add的语法链从0依序增加,cancel类型的action布局位置是固定的,和添加顺序无关,但其index与添加顺序有关。</p>    <p>对于复杂或者特殊的alertController,也可以根据action.title或者action区分响应。</p>    <h2>2.3.链式语法的“隐患”</h2>    <p>用过 Masonry 这个库的,应该都对链式语法不会太陌生。链式语法可以使得代码简化且逻辑清晰化。但是,其也有一定的“隐患”存在。</p>    <p>Masonry 应该是用的最多的一个自动布局的三方库,类似的还有 SDAutoLayout (这里只是举例,同样的三方还有很多,这个应该是除了 Masonry 外,用的相对多一些的一个)这样的,同样的链式语法,后者似乎更加简洁优雅。那为什么大名鼎鼎的 Masonry 不这么干呢?我想是因为“安全”。</p>    <p>用 SDAutoLayout 的Demo做一个实验:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/d14ff31c89edc21ab113e1986ac01c03.png"></p>    <p style="text-align: center;">view为nil导致的崩溃</p>    <p>这里把view0置为nil,之后运行,程序直接崩溃了。。。这类似于执行一个未赋值的空block。</p>    <p>有人可能会认为这样的实验没有意义,为nil了干嘛还要布局呢?其实这是笔者前阵子在封装一个链式库时遇到的问题:实际应用开发中,情况要复杂很多,有些view是动态添加的,甚至是根据接口数据动态创建的,如果在处理这类代码逻辑中稍有不慎,就会造成上述问题,给不存在的view进行布局,直接导致程序崩溃。。。</p>    <p>其实这也是代码书写规范的问题,针对这类动态view,在处理时,本就应该添加 if 条件判断的,不过有时容易忽视,或者他人接手相关代码时,也容易忽略。如果用 Masonry 的块配置布局,就不会发生这类问题,因为这种情况,对于 Masonry 那种写法,就是一个空指针执行一个方法,其结果就是不执行,而像 SDAutoLayout 这类的,不作判空处理,就会导致程序崩溃。这里尤其要注意。</p>    <h2>2.4.其他配置</h2>    <p>针对alert上的输入框,保持系统的添加方式,示例如下:</p>    <pre>  <code class="language-objectivec">[self jxt_showAlertWithTitle:@"title"                      message:@"message"            appearanceProcess:^(JXTAlertController * _NonnullalertMaker) {      alertMaker.      addActionDestructiveTitle(@"获取输入框1").      addActionDestructiveTitle(@"获取输入框2");         [alertMakersetAlertDidShown:^{          [self logMsg:@"alertDidShown"];//不用担心循环引用      }];      alertMaker.alertDidDismiss = ^{          [self logMsg:@"alertDidDismiss"];      };         [alertMakeraddTextFieldWithConfigurationHandler:^(UITextField * _NonnulltextField) {          textField.placeholder = @"输入框1-请输入";      }];      [alertMakeraddTextFieldWithConfigurationHandler:^(UITextField * _NonnulltextField) {          textField.placeholder = @"输入框2-请输入";      }];  } actionsBlock:^(NSIntegerbuttonIndex, UIAlertAction * _Nonnullaction, JXTAlertController * _NonnullalertSelf) {      if (buttonIndex == 0) {          UITextField *textField = alertSelf.textFields.firstObject;          [self logMsg:textField.text];//不用担心循环引用      }      else if (buttonIndex == 1) {          UITextField *textField = alertSelf.textFields.lastObject;          [self logMsg:textField.text];      }  }];  </code></pre>    <p>对于alert展示和关闭的回调,同样支持以block的方式配置。</p>    <p>如果 appearanceProcess 块不进行任何配置操作,即无按钮的alert,同样默认以toast模式处理。可通过设置 toastStyleDuration 属性,配置toast展示延迟时间。</p>    <h2>2.5.改变alertController的字体颜色</h2>    <p>可以通过KVC的方式访问alertController的私有属性,从而进行修改对应的字体的颜色,甚至字体。</p>    <p>对于 UIAlertAction ,可以用下面的方式修改字体颜色:</p>    <p>[alertAction setValue:[UIColor grayColor] forKey:@"titleTextColor"];</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/d88487748e87e58b5b1c63621529fd89.png"></p>    <p style="text-align: center;">修改UIAlertAction字体颜色的效果</p>    <p>但是就像前面说的,个人并不推荐这类方式,所以源码中没有提供相关的方法。</p>    <p>如果有对应的特殊需求,总体来说,还是自定义alert视图比较灵活,网上相关的开源库也有很多可以直接使用,不必总是纠结于系统的实现方式。</p>    <p>最后,欢迎使用JXTAlertManager,如果遇到任何问题,请及时联系作者。</p>    <p> </p>    <p> </p>