TextField付きのAlertを表示するCategory

# はじめに
久しぶりにのObjective-Cの投稿です。

今回は、UIAlertControllerでTextField付きのAlertを表示する処理を
UIViewControllerのクラス拡張として実装してみました。

AlertにTextFieldを付ける方法は、@d-kawahara さんの
[アラートコントローラーにテキストフィールドを入れる](https://qiita.com/d-kawahara/items/8464f5e2559a7f2125aa)
が参考になります。

# やること
1. AlertにTextFieldを付ける
2. TextFieldに文字が入力されたり、消されたら、通知する
3. TextFieldの入力文字数が0の場合、AlertのOKボタンを無効にする
4. OKボタンを押したら、入力した文字列をLabelに表示する

# TextField付きのAlertを表示する処理

```objc:UIViewController+Alert.h
#import <UIKit/UIKit.h>
#import <objc/runtime.h>

typedef void (^OKActionHandler)(NSString *outputText);

@interface UIViewController (Alert)
@property (nonatomic) UIAlertController *alertWithTextfield;
- (void)showAlertWithTextfield:(NSString *)defaultText
                   placeholder:(NSString *)placeholder
                    alertTitle:(NSString *)alertTitle
                  alertMessage:(NSString *)alertMessage
        textFieldTextDidChange:(SEL)aSelector
               okActionHandler:(OKActionHandler)okActionHandler;
@end
```

```objc:UIViewController+Alert.m
#import "UIViewController+Alert.h"

typedef void (^AlertActionHandler)(UIAlertAction *action);

@implementation UIViewController (Alert)

const void *alertWithTextfieldKey = @"alertWithTextfield";
@dynamic alertWithTextfield;

#pragma mark - Setter

- (void)setAlertWithTextfield:(UIAlertController *)alertWithTextfield {
    objc_setAssociatedObject(self,
                             alertWithTextfieldKey,
                             alertWithTextfield,
                             OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

#pragma mark - Getter

- (UIAlertController *)alertWithTextfield {
    return  objc_getAssociatedObject(self,
                                     alertWithTextfieldKey);
}

#pragma mark - Category Methods

- (void)showAlertWithTextfield:(NSString *)defaultText
                   placeholder:(NSString *)placeholder
                    alertTitle:(NSString *)alertTitle
                  alertMessage:(NSString *)alertMessage
        textFieldTextDidChange:(SEL)aSelector
               okActionHandler:(OKActionHandler)okActionHandler {
    
    self.alertWithTextfield = [UIAlertController alertControllerWithTitle:alertTitle
                                                                  message:alertMessage
                                                           preferredStyle:UIAlertControllerStyleAlert];
    
    __weak typeof(self) weakSelf = self;
    [self.alertWithTextfield addTextFieldWithConfigurationHandler:^(UITextField *textField) {
        __strong typeof(self) strongSelf = weakSelf;
        if (!strongSelf) {
            return;
        }
        
        textField.placeholder = placeholder;
        textField.text = defaultText;
        
        // TextFieldに通知を登録
        [NSNotificationCenter.defaultCenter addObserver:strongSelf
                                               selector:aSelector
                                                   name:UITextFieldTextDidChangeNotification
                                                 object:textField];
    }];
    
    // OKタップ時の処理
    AlertActionHandler okHandler = ^(UIAlertAction *action) {
        
        __strong typeof(self) strongSelf = weakSelf;
        if (!strongSelf) {
            return;
        }
        UITextField *alertTextField = strongSelf.alertWithTextfield.textFields.firstObject;
        okActionHandler(alertTextField.text);
        [NSNotificationCenter.defaultCenter removeObserver:alertTextField];
    };
    UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"OK"
                                                       style:UIAlertActionStyleDefault
                                                     handler:okHandler];
    
    // キャンセルタップ時の処理
    AlertActionHandler cancelHandler = ^(UIAlertAction *action) {
        __strong typeof(self) strongSelf = weakSelf;
        if (!strongSelf) {
            return;
        }
        
        UITextField *alertTextField = strongSelf.alertWithTextfield.textFields.firstObject;
        // TextFieldに登録した通知を削除
        [NSNotificationCenter.defaultCenter removeObserver:alertTextField];
    };
    
    UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"キャンセル"
                                                           style:UIAlertActionStyleCancel
                                                         handler:cancelHandler];
    
    // 引数のtextの長さによって、OKボタンの初期状態を設定
    BOOL enabledOKButton = defaultText && defaultText.length > 0;
    okAction.enabled = enabledOKButton;
    
    [self.alertWithTextfield addAction:cancelAction];
    [self.alertWithTextfield addAction:okAction];
    [self presentViewController:self.alertWithTextfield animated:YES completion:nil];
}
@end
```

# ViewControllerで呼び出す

```objc:ViewController.m
#import "ViewController.h"
#import "UIViewController+Alert.h"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UILabel *outputLabel;
@end

@implementation ViewController

#pragma mark - View Life Cycle

- (void)viewDidLoad {
    [super viewDidLoad];
}

#pragma mark - IBActions

/**
 Show Alertボタンがタップされた時の処理
 */
- (IBAction)didTapShowAlertButton:(UIButton *)sender {
    
    __weak typeof(self) weakSelf = self;
    [self showAlertWithTextfield:self.outputLabel.text
                     placeholder:@"テキストを入力してください。"
                      alertTitle:@"たいとる"
                    alertMessage:@"めっせーじ"
          textFieldTextDidChange:@selector(alertTextFieldTextDidChange:)
                 okActionHandler:^(NSString *outputText) {
                     // AlertのTextFieldで入力された文字をoutputLabelに表示
                     weakSelf.outputLabel.text = outputText;
                 }];
}

#pragma mark - Selector

/**
 Alert内のTextFieldの文字が変更された時に呼ばれるセレクタメソッド
 */
- (void)alertTextFieldTextDidChange:(NSNotification *)notification {
    UITextField *textField = notification.object;
    
    // 入力後の文字数が0より大きければ、AlertのOKボタン有効
    // 0文字の場合は、AlertのOKボタン無効
    NSUInteger textLength = textField.text.length;
    BOOL enabledAlertOKButton = textLength > 0;
    self.alertWithTextfield.actions.lastObject.enabled = enabledAlertOKButton;
}

@end
```

# 動作
![Alert_demo.gif](https://qiita-image-store.s3.amazonaws.com/0/103695/cb11e84f-9554-3c70-f822-e718efe79442.gif)

# さいごに
Categoryは少しコードが長くなってしまいましたが、ViewController側の実装が簡単になりました。
また、Categoryに書くことによって他のクラスでも使いまわせるようになりました。

本記事で紹介したコードは、Githubにあげています。
[ios-objc-uialertcontroller-uitextfield-demo](https://github.com/stv-yokudera/ios-objc-uialertcontroller-uitextfield-demo)