[iOS] UIAlertController の UITextField で文字入力があるまでボタンを押せないようにする

UIAlertView が iOS8 で DEPRECATED されて、代わりに UIAlertController を利用する方法が推奨された。UIAlertView では iOS5 から UITextField を使った文字入力が可能になっていた。UIAlertController でも同様の機能は提供されている。

UIAlertController ではボタン押下時のアクションハンドラが delegate から Blocks を使った方法に変わった。そのため、UITextField の enabled プロパティに簡単にアクセスする手段がなくなった。UIAlertView では、文字入力があるまで「OK」などのボタンを押せなくできていたが、UIAlertController ではできなくなった。（不可能と言うよりは、簡単ではなくなった。）

以下、上記の問題を解決するために調査した内容を元に、汎用的に書き換えたソースコードの定型文を示す。自作のプログラムにそのまま取り込んで、文字列リテラルやボタン押下時などの必要な箇所を変更すれば、すぐに利用できるだろう。

```objectivec
-(void)prompt
{
   __weak typeof(self) weakSelf = self;

  @autoreleasepool {
    UIAlertController * alertController =
             [UIAlertController alertControllerWithTitle:@"新規フォルダ"
                                message:@"フォルダ名を入力して下さい"
                                preferredStyle:UIAlertControllerStyleAlert];

    void (^configurationHandler)(UITextField *) = ^(UITextField * textField) {
      textField.placeholder = @"フォルダ名";
      // ここで UITextField の text が変更したときの通知を受信する設定を実施
      [[NSNotificationCenter defaultCenter]
        addObserver:weakSelf
        selector:@selector(alertTextFieldDidChange:)
        name:UITextFieldTextDidChangeNotification
        object:textField];
    };
    [alertController addTextFieldWithConfigurationHandler:configurationHandler];

    void (^cancelHandler)(UIAlertAction *) = ^(UIAlertAction * action) {
      // UITextField 変更通知は不要
      [[NSNotificationCenter defaultCenter]
        removeObserver:weakSelf
        name:UITextFieldTextDidChangeNotification
        object:nil];
    };
    UIAlertAction * cancelAction =
             [UIAlertAction actionWithTitle:@"中止"
                            style:UIAlertActionStyleCancel
                            handler:cancelHandler];
    [alertController addAction:cancelAction];

    void (^createHandler)(UIAlertAction *) = ^(UIAlertAction * action) {
      UITextField * textField = alertController.textFields[0];
      if (textField.text.length > 0) {
        dispatch_block_t mainBlock = ^{
          // ここに UI の更新やデータベースへの追記などを実施するコードを記述する
        };
        dispatch_async(dispatch_get_main_queue(), mainBlock);
      }
      // 通知はもう要らない
      [[NSNotificationCenter defaultCenter]
        removeObserver:weakSelf
        name:UITextFieldTextDidChangeNotification
        object:nil];
    };
    UIAlertAction * createAction =
            [UIAlertAction actionWithTitle:@"作成"
                           style:UIAlertActionStyleDefault
                           handler:createHandler];
    createAction.enabled = NO; // 最初は「作成」ボタンは押せない
    [alertController addAction:createAction];

    [self presentViewController:alertController animated:YES completion:nil];
  }
}

#pragma mark - NSNotification handler
// ここで UITextField の入力処理を行う
-(void)alertTextFieldDidChange:(NSNotification *)notification
{
  UIAlertController * alertController = (UIAlertController *)self.presentedViewController;
  if (alertController) {
    UITextField *   textField    = alertController.textFields.firstObject;
    UIAlertAction * createAction = alertController.actions.lastObject;
    // 文字入力があればボタンを押せるようにする。
    // ここの値を 0 から 8 などにすれば、8文字以上の入力がないと有効にならないようにもできる。
    createAction.enabled = textField.text.length > 0;
  }
 }
```

ソースコードの利用は、上記をそのまま自作のプログラムにコピーした場合は、次の一文で呼び出せる。

```objctivec
[self prompt];
```

上記コードの NSNotification ハンドラの alertTextFieldDidChange 内の UITextFiled に対する条件処理を変更すれば、n文字以上m文字未満の場合など、簡単に応用可能である。以下は、よくあるパスワード入力の場合の修正箇所のコード。

```objectivec
void (^configurationHandler)(UITextField *) = ^(UITextField * textField) {
  textField.placeholder = @"";
  textField.secureTextEntry = YES; // 入力文字列を「*」でマスクする（追加部分！）

  [[NSNotificationCenter defaultCenter]
    addObserver:weakSelf
    selector:@selector(alertTextFieldDidChange:)
    name:UITextFieldTextDidChangeNotification
    object:textField];
};

#pragma mark - NSNotification handler
-(void)alertTextFieldDidChange:(NSNotification *)notification
{
  UIAlertController * alertController = (UIAlertController *)self.presentedViewController;
  if (alertController) {
    UITextField *   textField    = alertController.textFields.firstObject;
    UIAlertAction * createAction = alertController.actions.lastObject;
    // パスワードは 10文字以上の入力を必須とする。
    createAction.enabled = textField.text.length > 10; // （修正箇所！）
  }
}
```

定型文を使いまわせば、お手軽に応用ができるね。