UIAlertController的使用

StuBergmann 7年前
   <p>UIAlertController 继承自 UIViewController ,用以向用户显示提醒信息。自iOS 8开始,Apple用 UIAlertController 替换了 UIAlertView 和 UIAlertSheet 。</p>    <p>警报控制器( UIAlertController )有 <strong>警告框</strong> ( Alert View )和 <strong>操作表</strong> ( Action Sheet )两种形式。 Alert View 和 Action Sheet 虽然呈现样式不同,但创建步骤一样。步骤如下:</p>    <ol>     <li>创建 UIAlertContorller</li>     <li>向警报控制器添加按钮</li>     <li>显示 UIAlertContorller</li>    </ol>    <p>下面通过创建一个demo来学习一下。</p>    <p>打开Xcode,创建新的工程。选择 <strong>iOS</strong> 一栏下的 <strong>Application</strong> 中的 <strong>Single View Application</strong> 模板,点击 <em>Next</em> 。在 <strong>Product Name</strong> 中填写 <strong>AlertController</strong> ,点击 <em>Next</em> ,选择文件存放位置,点击 <em>Create</em> 创建工程。</p>    <p>打开刚创建工程的 <em>storyboard</em> ,在 <em>storyboard</em> 中自上而下依次添加以下控件,内容为 UIAlertControllerStyleAlert 的 UILabel ,标题为 Show Alert 的 UIButton ,标题为 Login Alert 的 UIButton ,内容为 UIAlertControllerStyleActionSheet 的 UILabel ,标题为 Action Sheet 的 UIButton 。如下图:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/e9842e40392573ef5ed65228d9ad6d39.png"></p>    <p style="text-align:center">AlertControllerStoryboard.png</p>    <p>把 UIButton 拖拽到 ViewController.m 的接口部分,类型为 IBAction 。完成后代码如下:</p>    <pre>  <code class="language-objectivec">#import "ViewController.h"    @interface ViewController ()    - (IBAction)showAlertView:(UIButton *)sender;  - (IBAction)showLoginAlertView:(UIButton *)sender;  - (IBAction)showActionSheet:(UIButton *)sender;    @end</code></pre>    <p><strong>简单对话框样式</strong></p>    <p><strong>1.创建警报控制器</strong></p>    <p>创建 UIAlertContorller 非常简单,不需要设置代理、不需要指定按钮。下面先在 showAlertView: 方法中,创建 UIAlertContorller 。</p>    <pre>  <code class="language-objectivec">- (IBAction)showAlertView:(UIButton *)sender  {      // 1.创建UIAlertController      UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Alert Title"                                                                               message:@"The message is ..."                                                                        preferredStyle:UIAlertControllerStyleAlert];  }</code></pre>    <p>这里的 preferredStyle: 参数有 UIAlertControllerStyleAlert 和 UIAlertControllerStyleActionSheet 两种,这里我们要创建的是 Alert View 所以使用第一种。</p>    <p><strong>2.添加按钮</strong></p>    <p>使用 actionWithTitle: style: handler: 方法创建 UIAlertAction 对象,之后把对象添加到警报控制器。</p>    <p>UIAlertAction 对象由标题、样式和用户单击该按钮时运行的代码块三部分组成。 UIAlertActionStyle 有三种样式,样式一 UIAlertActionStyleCancel ,用于取消操作、不作任何修改,就是常见的 <strong>取消</strong> 按钮;样式二 UIAlertActionStyleDefault ,按钮的默认样式;第三种是 UIAlertActionStyleDestructive ,用于对数据进行更改或删除的操作,这种样式的按钮标题会自动使用红色显示。</p>    <p>在 showAlertView: 方法中添加 <strong>Cancel</strong> 按钮和 <strong>OK</strong> 按钮。</p>    <pre>  <code class="language-objectivec">- (IBAction)showAlertView:(UIButton *)sender  {      ...      // 2.创建并添加按钮      UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {          NSLog(@"OK Action");      }];      UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {          NSLog(@"Cancel Action");      }];        [alertController addAction:okAction];           // A      [alertController addAction:cancelAction];       // B  }</code></pre>    <p><strong>3.显示 UIAlertContorller</strong></p>    <p>显示 UIAlertContorller 。</p>    <pre>  <code class="language-objectivec">- (IBAction)showAlertView:(UIButton *)sender  {      ...      // 3.呈现UIAlertContorller      [self presentViewController:alertController animated:YES completion:nil];  }</code></pre>    <p>在视图控制器中显示如下:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/ea4f6c3e039e52c18421ee13424c3e78.png"></p>    <p style="text-align:center">AlertView</p>    <p>改变上面 addAction: 方法顺序,运行app,你会发现 Alert View 中按钮顺序不变。当 Alert View 样式中有 <strong>Cancel</strong> 按钮时, <strong>Cancel</strong> 按钮总是显示在左侧,与添加按钮的顺序无关。</p>    <p>在 showAlertView: 方法中再添加一个 UIAlertActionStyleDestructive 样式的 <strong>Reset</strong> 按钮。</p>    <pre>  <code class="language-objectivec">- (IBAction)showAlertView:(UIButton *)sender  {      ...      UIAlertAction *resetAction = [UIAlertAction actionWithTitle:@"Reset" style:UIAlertActionStyleDestructive handler:^(UIAlertAction * _Nonnull action) {          NSLog(@"Reset Action");      }];      [alertController addAction:resetAction];        // C      [alertController addAction:okAction];           // A      [alertController addAction:cancelAction];       // B      ...  }</code></pre>    <p>在视图控制器中显示如下:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/c9dfc0a603797ba71d4484421283c431.png"></p>    <p>当 Alert View 中存在一个或两个按钮时,按钮会水平排布;按钮大于两个时会像 Action Sheet 那样竖列展示。把上面 addAction: 顺序改变为B、A、C,运行app,视图控制器显示如下:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/2ba2514e66fc7b601b3d144fa4228a1a.png"></p>    <p>可以看到上面只要有 UIAlertActionStyleCancel 样式的按钮,该按钮总是在最底部,其他按钮顺序由添加顺序决定。如果包含 UIAlertActionStyleDestructive 样式的按钮,一般先添加,以便在第一个位置显示。每一个警报控制器只能包含一个 <strong>Cancel</strong> 按钮,如果你添加了两个或多个,在运行时会抛出 NSInternalInconsistencyException 的异常。</p>    <p><strong>登录文本框</strong></p>    <p>UIAlerController 的另一个用途是我们可以向警报控制器中添加任意数量的 UITextField 作为警报控制器内容视图中的一部分。比如常见的登陆框。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/28a021cdc7f7174f57f58ced185adeb6.png"></p>    <p>为了创建一个上图中的登录框,我们需要为警报控制器添加两个文本框。每一个文本框添加合适的占位符以提示需要输入文本信息,并为需要输入密码的文本框启用安全文本,以确保密码安全。更新后的 showLoginAlertView: 代码如下:</p>    <pre>  <code class="language-objectivec">- (IBAction)showLoginAlertView:(UIButton *)sender  {      // 1.创建UIAlertController      UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Login"                                                                               message:@"Enter Your Account Info Below"                                                                        preferredStyle:UIAlertControllerStyleAlert];        // 2.1 添加文本框      [alertController addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {          textField.placeholder = @"username";      }];      [alertController addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {          textField.placeholder = @"password";          textField.secureTextEntry = YES;      }];  }</code></pre>    <p>继续在 showLoginAlertView: 方法中添加 <strong>Cancel</strong> 按钮和 <strong>Login</strong> 按钮,在点击 <strong>Login</strong> 按钮时获取文本框中的账号和密码并输出到控制台。</p>    <pre>  <code class="language-objectivec">- (IBAction)showLoginAlertView:(UIButton *)sender  {      ...      // 2.2  创建Cancel Login按钮      UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {          NSLog(@"Cancel Action");      }];      UIAlertAction *loginAction = [UIAlertAction actionWithTitle:@"Login" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {          UITextField *userName = alertController.textFields.firstObject;          UITextField *password = alertController.textFields.lastObject;            // 输出用户名 密码到控制台          NSLog(@"username is %@, password is %@",userName.text,password.text);      }];  }</code></pre>    <p>上面的代码中,警报控制器中的 UITextField 的顺序由添加顺序决定。</p>    <p>最后添加按钮,显示警报控制器。</p>    <pre>  <code class="language-objectivec">- (IBAction)showLoginAlertView:(UIButton *)sender  {      ...      // 2.3 添加按钮      [alertController addAction:cancelAction];      [alertController addAction:loginAction];        // 3.显示警报控制器      [self presentViewController:alertController animated:YES completion:nil];  }</code></pre>    <p>现在运行app,在第一个文本框中输入 pro648 ,在第二个文本框内输入 x ,点击 <strong>Login</strong> 按钮,控制台输出如下:</p>    <pre>  <code class="language-objectivec">username is pro648, password is x</code></pre>    <p>在实际应用中我们一般会对用户名和密码长度进行限制,当长度不足时应该禁用 <strong>Login</strong> 按钮。我们可以通过为文本框添加一个 UIControllEventEditingChanged 响应事件来实现。记得在添加按钮前先禁用按钮。更新后代码如下:</p>    <pre>  <code class="language-objectivec">- (IBAction)showLoginAlertView:(UIButton *)sender  {      ...      // 2.1 添加文本框      [alertController addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {          textField.placeholder = @"username";            [textField addTarget:self action:@selector(alertUserAccountInfDidChange:) forControlEvents:UIControlEventEditingChanged];     // 添加响应事件      }];      [alertController addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {          textField.placeholder = @"password";          textField.secureTextEntry = YES;            [textField addTarget:self action:@selector(alertUserAccountInfDidChange:) forControlEvents:UIControlEventEditingChanged];     // 添加响应事件      }];        ...      // 2.3 添加按钮      loginAction.enabled = NO;   // 禁用Login按钮      [alertController addAction:cancelAction];      [alertController addAction:loginAction];      ...  }    - (void)alertUserAccountInfDidChange:(UITextField *)sender  {      UIAlertController *alertController = (UIAlertController *)self.presentedViewController;        if (alertController)      {          UITextField *userName = alertController.textFields.firstObject;          UIAlertAction *loginAction = alertController.actions.lastObject;          UITextField *password = alertController.textFields.lastObject;          if (userName.text.length>3 && password.text.length > 6)     // 用户名必须大于三位 密码必须大于六位          {              loginAction.enabled = YES;          }      }  }</code></pre>    <p>UIAlertController 中的 textFields 和 actions 均是数组类型,第一个添加的对象index为 0 。之后按照添加的顺序index依次加1,虽然前面说到 <strong>Cancel</strong> 按钮一般显示在左侧(横排)或底部(竖排),但并不代表它在数组中的位置是第一个或最后一个,其index是由添加的顺序决定。你可以根据 username 字符串长度来禁用 <strong>Login</strong> 按钮进行测试。</p>    <p>现在只有在用户名大于三位、密码大于六位时, <strong>Login</strong> 按钮才可以点击。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/e20aa76201f288425563762e19f93079.png"></p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/b8c555b780aea2cf62bf2e0a111588c9.png"></p>    <p><strong>Action Sheet</strong></p>    <p>操作表一般用于为用户提供一组可供选择的操作选项,如删除,恢复等。一般根据设备尺寸大小决定呈现形式,在iPhone上,操作表由底部滑出,在iPad上,操作表以弹出框形式出现。</p>    <p>创建操作表的方法与警告框类似,唯一不同在于 preferredStyle: 参数的选择。在 showActionSheet: 方法中创建操作表。</p>    <pre>  <code class="language-objectivec">- (IBAction)showActionSheet:(UIButton *)sender  {      // 1.创建UIAlertController      UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Action Sheet"                                                                               message:@"Deleted data can't be restored"                                                                        preferredStyle:UIAlertControllerStyleActionSheet];  }</code></pre>    <p>下面创建并添加按钮,最后呈现警报控制器。</p>    <pre>  <code class="language-objectivec">- (IBAction)showActionSheet:(UIButton *)sender  {      ...      // 2.1 创建按钮      UIAlertAction *deleteAction = [UIAlertAction actionWithTitle:@"Delete" style:UIAlertActionStyleDestructive handler:^(UIAlertAction * _Nonnull action) {          NSLog(@"Delete Action");      }];      UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {          NSLog(@"Cancel Action");      }];        // 2.2 添加按钮      [alertController addAction:deleteAction];      [alertController addAction:cancelAction];        // 3.显示警报控制器      [self presentViewController:alertController animated:YES completion:nil];  }</code></pre>    <p>运行app,操作表展示如下:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/09a6c08102ce86f544790137cabc8730.png"></p>    <p>如果 Action Sheet 中有取消按钮,取消按钮每次都会在底部显示,其他按钮会按照添加的顺序显示。在 Action Sheet 内不能添加文本框。如果你添加了文本框,在运行时会抛出下面的异常提醒:</p>    <p><em>*</em> Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Text fields can only be added to an alert controller of style UIAlertControllerStyleAlert'</p>    <p>如上面说到的在iPad中, Action Sheet 以弹出框的形式呈现。弹出框总是需要一个锚点,锚点可以是源视图,也可以是按钮。在这个demo中,我们用按钮触发弹出框,所以这里将把按钮作为锚点。 showActionSheet: 方法更新后如下:</p>    <pre>  <code class="language-objectivec">- (IBAction)showActionSheet:(UIButton *)sender  {      ...      UIPopoverPresentationController *popover = alertController.popoverPresentationController;      if (popover)      {          popover.sourceView = sender;          popover.sourceRect = sender.bounds;          popover.permittedArrowDirections = UIPopoverArrowDirectionAny;      }        // 3.显示警报控制器      [self presentViewController:alertController animated:YES completion:nil];  }</code></pre>    <p>如果在iPad中没有添加上面方法,运行时会出现下面崩溃提示:</p>    <p><em>*</em> Terminating app due to uncaught exception 'NSGenericException', reason: 'Your application has presented a UIAlertController (<UIAlertController: 0x7f88c85221f0>) of style UIAlertControllerStyleActionSheet. The modalPresentationStyle of a UIAlertController with this style is UIModalPresentationPopover. You must provide location information for this popover through the alert controller's popoverPresentationController. You must provide either a sourceView and sourceRect or a barButtonItem. If this information is not known when you present the alert controller, you may provide it in the UIPopoverPresentationControllerDelegate method -prepareForPopoverPresentation.'</p>    <p>现在, Action Sheet 以触发它的按钮为锚点,以弹出框形式展现。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/c4f04684a9102bda1314b65d7947293e.png"></p>    <p>当 Action Sheet 以弹出框形式展现时, UIAlertController 会自动取消 <strong>Cancel</strong> 按钮,用户可以通过点击弹出框以外的区域来取消弹出框。</p>    <p><strong>退出警报控制器</strong></p>    <p>警报控制器会在用户点击按钮后自动消失,如果需要,也可以在代码中实现手动退出。一般在app进入后台时警告框和选择表并没有退出,我们可以通过在 viewDidLoad 方法中添加 通知中心 ,当观察者接收到app进入后台时,退出警报控制器。</p>    <pre>  <code class="language-objectivec">- (void)viewDidLoad  {      [super viewDidLoad];        // app 进入后台后隐藏警报控制器      [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidEnterBackgroundNotification object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) {          [self.presentedViewController dismissViewControllerAnimated:YES completion:nil];      }];  }    - (void)dealloc  {      // 移除观察者      [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil];  }</code></pre>    <p>最后一定记得移除观察者,否则会引起崩溃。</p>    <p><strong>总结</strong></p>    <p>下面总结下 Alert View 和 Action Sheet 的异同。</p>    <p>警告框 Alert View</p>    <ul>     <li>一般显示在当前视图控制器的中心,点击警告框以外区域不能隐藏警告控制器。</li>     <li>可以添加任意数量文本框。</li>     <li>有一个或两个按钮时,横向排布,如果有 <strong>Cancel</strong> 按钮,则 <strong>Cancel</strong> 按钮显示在左侧;有两个以上按钮时,竖列排布,如果有 <strong>Cancel</strong> 按钮,则 <strong>Cancel</strong> 按钮显示在最底部。其他按钮按照添加顺序排布。</li>    </ul>    <p>操作表 Action Sheet</p>    <ul>     <li>在iPhone中一般以滑出的形式显示在当前控制器的底部,点击警告框以外区域可以隐藏警告控制器。</li>     <li>在iPad中以popover方式、以源视图为锚点显示,点击选择表以外的区域可以隐藏警告控制器。</li>     <li>不能添加文本框。</li>     <li>按钮竖列排布,在iPhone中, <strong>Cancel</strong> 按钮默认在底部显示;在iPad中, <strong>Cancel</strong> 按钮默认不显示。</li>    </ul>    <p>Alert View 和 Action Sheet 都只能包含一个 UIAlertActionStyleCancel 样式的按钮,否则运行时会崩溃。 UIAlertController 类只能原样使用,不支持子类化。此类的视图层次结构是私有的,不能修改。</p>    <p> </p>    <p>参考资料:</p>    <ol>     <li><a href="/misc/goto?guid=4959741689553878271" rel="nofollow,noindex">UIAlertController Changes in iOS 8</a></li>     <li><a href="https://developer.apple.com/reference/uikit/uialertcontroller?changes=latest_major&language=objc#overview" rel="nofollow,noindex">UIAlertController</a></li>    </ol>    <p> </p>    <p>来自:http://www.jianshu.com/p/a5307dd8c424</p>    <p> </p>