Alert,ActionSheetの表示処理がスッキリかけるUIAlertControllerのラッパーライブラリを作りました

AlertやActionSheetの表示をする時にUIAlertControllerを使いますが、毎回`UIAlertController`を作って、`UIAlertAction`を作って追加して...という記述が長くなったりして面倒ですよね。
ということで、コードの記述量を減らしつつ、スッキリ書けるラッパーライブラリを作ってみました。

- [Alertift](https://github.com/sgr-ksmt/Alertift)

Swift3.1以降、iOS9以上で動作します。**CocoaPods** or **Carthage**で導入してください。

## BeforeとAfter
では実際にどう変わるのか同じ内容のAlertを出す処理のbeforeとAfterを示してみます。

- Before

```swift:従来通りの書き方
let alert = UIAlertController(title: "Confirm", message: "Do you want to delete this post?", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Delete", style: .destructive) { [unowned self] _ in
    self.delete(post)        
})
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
self.present(alert, animated: true, completion: nil)
```

- After

```swift:Alertiftを使った書き方
Alertift.alert(title: "Confirm", message: "Do you want to delete this post?")
    .action(.destructive("Delete")) { [unowned self] in
        self.delete(post)
    }
    .action(.cancel("Cancel"))
    .show(on: self)
```

行数は変わらないものの、全体的に **スッキリ** と書けるようになっています。`UIAlertController` や `UIAlertAction` を書くことから解放されています。

また、アクションの追加や、後述する`UITextField`のハンドリングの処理やpopoverの設定、Alert(ActionSheet)の色の変更等がメソッドチェーン的に繋いでいくことができて、最後に`show`メソッドを呼び出せば表示されます。


## 共通: アクションの追加と表示
アクションの追加を行うには、`action`関数にアクションの種類と必要に応じてボタンを押したときのハンドラを渡します。複数のアクションを追加する場合はメソッドチェーンでどんどん繋いでいきます。
actionは`default`, `cancel, `destructive`の3種類でそれぞれボタンのタイトルをAssociated Valueとして渡します。

```swift
Alertift.alert(title: "Sample", message: "sample alert.")
    .action(.default("通常のボタンです")) {
        print("!!!")
    }
    .action(.cancel("キャンセルボタン"))
    .action(.destructive("破壊的な行動を行う時のボタン(赤文字)"))
```

表示する場合は最後に`show(on:completion:)`関数を呼び出します。`on:` にアラートを表示するViewControllerを指定します。

```swift
Alertift.alert(title: "Sample", message: "sample alert.")
    .action(.default("OK"))
    .show(on: self)

Alertift.actionSheet(title: "Sample", message: "sample action sheet.")
    .action(.default("1"))
    .action(.default("2"))
    .show(on: self)
```

showに引数を渡さなかった場合は `UIApplication.shared.keyWindow?.rootViewController` 上に表示しようと試みます。

```swift
Alertift.alert(title: "Sample", message: "sample alert.")
    .action(.default("OK"))
    .show() // UIApplication.shared.keyWindow?.rootViewController 上に表示
```

### *finally*
いずれかのアクションボタンを押した時に最後に呼び出す処理を`finally`関数で追加することができます。ハンドラの引数には押された`UIAlertAction`とそのアクションのindexが渡ってきます。
また、actionのハンドラも呼ばれるので、

```swift
Alertift.actionSheet(message: "Which food do you like?")
    .action(.default("🍣")) {
        print("Sushi")
    }
    .action(.default("🍎"))
    .action(.default("🍖"))
    .action(.default("🍅"))
    .action(.cancel("None of them"))
    .finally { action, index in
        if action.style == .cancel {
            return
        }
        print(action.title, index)
    }
    .show()
```

と記述して🍣を押した場合の出力結果は、

```swift
"Sushi"
"🍣, 0"
```

となります。

## Alert
### `UITextField`を使う

アラートにUITextFieldを表示するには、`textField(configurationHandler:)`を使います。ハンドラでUITextFieldが渡ってくるので、それを基に`UITextField`をカスタマイズすることも可能です。

```swift
Alertift.alert(title: "Sign in", message: "Input your ID and Password")
    .textField { textField in
        textField.placeholder = "ID"
    }
```


`UITextField`に入力された文字列を受け取るには2通りの方法があります。

- `handleTextFieldTextDidChange(textFieldTextDidChangeHandler:)`で変更を受け取る

`handleTextFieldTextDidChange(textFieldTextDidChangeHandler:)`で追加したいずれかのtextfieldのtextの変更を受け取ることができます。ハンドラの引数には、変更があった `UITextField`とその`UITextField`のindex番号が渡ってきます。

```swift
Alertift.alert(title: "Sign in", message: "Input your ID and Password")
    .handleTextFieldTextDidChange { textField, index in
        let text = textField.text ?? ""
        print("\(index), \(text)")
    }
```

なお、`UITextField`の変更の監視は内部で行っているため、Alertiftのメソッドチェーンの外で別途delegateや`NotificationCenter`で監視する必要はありません。

- `action(_: textFieldsHandler:)`でボタンが押された時に`UITextField`に入力された値を取得する

通常はアクションの ハンドラでは引数なしのものを用いますが、`textFieldsHandler:`として、UITextFieldの配列を引数にもつclosureを渡してあげると、ボタンが押されたときに追加した`UITextField`の配列を追加で受け取ることができます。

```swift
.action(.default("Sign in"), textFieldsHandler: { textFields in
        let id = textFields?.first?.text ?? ""
        let password = textFields?.last?.text ?? ""
        let title = "Sign in successfully"
        let message = "ID: \(id)\nPassword: \(password)"
        Alertift.alert(title: title, message: message)
            .action(.default("OK"))
            .show()
    })
```


最終的な例として、以下にユーザーのIDとpassword入力を行わせ、入力した文字列を取得する処理を示します。

```swift
Alertift.alert(title: "Sign in", message: "Input your ID and Password")
    .textField { textField in
        textField.placeholder = "ID"
    }
    .textField { textField in
        textField.placeholder = "Password"
        textField.isSecureTextEntry = true
    }
    .handleTextFieldTextDidChange { textField, index in
        let text = textField.text ?? ""
        print("\(index), \(text)")
    }
    .action(.cancel("Cancel"))
    .action(.default("Sign in"), textFieldsHandler: { textFields in
        let id = textFields?.first?.text ?? ""
        let password = textFields?.last?.text ?? ""
        let title = "Sign in successfully"
        let message = "ID: \(id)\nPassword: \(password)"
        Alertift.alert(title: title, message: message)
            .action(.default("OK"))
            .show()
    })
.show()
```

![img2.png](https://qiita-image-store.s3.amazonaws.com/0/27802/dcebf1a5-ec70-ca00-58ed-956253714a81.png)


### その他
また、iOS9から使用可能な、*preferredAction*を変更する実装も備えているので、キャンセルのボタンではない任意のボタンの文字を太字にすることも可能です。アクションを追加するときに、`isPreferred`をtrueにします。

```swift
Alertift.alert(title: "Sample 1", message: "Simple alert!")
    .action(.default("OK"), isPreferred: true)
    .action(.cancel("cancel"))
    .show()
```

![img.png](https://qiita-image-store.s3.amazonaws.com/0/27802/f717bf37-cdc3-9a58-cb12-907fb9135849.png)



## ActionSheet

### iPadの場合
iPadの場合(或いはUniversal対応アプリの場合)は別途popoverのanchorViewを指定する必要があります。具体的には、`alertController`の

- `popoverPresentationController.sourceView`
- `popoverPresentationController.sourceRect`
- (或いは) `popoverPresentationController.barButtonItem`

に適切なview,rectを指定する必要があります。そのための関数も別途用意しています。`popover(anchorView:)`を用いるのが1番簡単です。
万一設定し忘れてもcrashしないように内部的に処理しています。(表示位置は左上になってしまいますが。)
また、以下のように実行端末に応じてコードを分ける必要はありません。(iPhoneの場合は、`alertController.popoverPresentationController` がnilになるため影響が及ばない。)

```swift
// good!
Alertift.actionSheet(message: "Which food do you like?")
    .popover(anchorView: button)
    .action(...)
    .show()   


// not good
if isIphone {
    Alertift.actionSheet(message: "Which food do you like?")
        .action(...)
        .show()
} else {
    Alertift.actionSheet(message: "Which food do you like?")
        .popover(anchorView: button) // この部分の有無を端末判定で分ける必要はなし
        .action(...)
        .show()   
}
```

## 色変更
また、Alertのtitleやmessageの文字色、ボタンの文字色、背景色を変えることができます。
private APIは使っていません。

```swift
Alertift.alert(title: "Red color title", message: "Message text color is blue")
    .titleTextColor(.red)
    .messageTextColor(.blue)
    .backgroundColor(.lightGray)
    .buttonTextColor(.orange)
    .action(.default("orange color button"))
    .show()
```

![img6.png](https://qiita-image-store.s3.amazonaws.com/0/27802/9a617da6-8b65-24fa-47b7-888dc9d382d3.png)



## よかったら
使ってみて下さい！PR等もお待ちしております。