ActionSheet に対する入力を Observable として受け取る

# 問題

RxSwift で入力を Observable として扱っていると UIAlertController も入力を Observable として返して欲しいと思うことがあります。

たとえば、UITableViewCell を削除するイベントきっかけで 確認のための ActionSheet を表示するときです。
セルに対する入力は Observable でイベント受け取りできますが ActionSheet に対する入力は UIAlertController だと Observable で受け取りできません。

```
        tableView.rx_itemDeleted  //  セル削除のイベントを Observable として受けとる
            .asObservable()
            .subscribeNext { [unowned self] (indexPath) in
			    let alert = UIAlertController(title:"Are you sure?",　message: "", preferredStyle: .ActionSheet)
 			    let removeAction = UIAlertAction(title: "Delete", style: .Destructive) { [unowned self] _ in
　　　　　　　　　 // Delete ボタンを押下したときの処理 ..
			    }
 		       	alert.addAction(removeAction)
 		       	presentViewController(alert, animated: true, completion: nil)  // ActionSheet を表示
        }.addDisposableTo(disposeBag)
```

ここで、Delete ボタン押下のイベントを Observable として受け取って rx_itemDeleted の Observable と flatMap した形で処理を記述したいということです。

# 解決方法

ActionSheet に対する入力を Observable として受け取る方法は [RxSwift/RxExample](https://github.com/ReactiveX/RxSwift/tree/1c47f0a79231f889a6c954023d857a3b5344ac88/RxExample) のコードのなかにありました。

>     func promptFor<Action : CustomStringConvertible>(message: String, cancelAction: Action, actions: [Action]) -> Observable<Action> {
> https://github.com/ReactiveX/RxSwift/blob/1c47f0a79231f889a6c954023d857a3b5344ac88/RxExample/RxExample/Services/Wireframe.swift#L58

これを UIViewController の extension に実装したものを使って 最初のサンプルコードを flatMap で書き直すことができます: 

```
        tableView.rx_itemDeleted
            .asObservable()
            .flatMap { [unowned self] (indexPath) -> Observable<(Action, NSIndexPath)> in
　　　　　　　　　　// promptFor は ActionSheet を表示し・入力を Observable で返す
                return self.promptFor(UIAlertController(title: "Are you sure?",
　　　　　　　　　　　　　　 message: "",　preferredStyle: .ActionSheet), // ActionSheet には ..
                        cancelAction: Action.Cancel("Cancel"),       // キャンセルボタンと ..
                        actions: [Action.Confirm("OK")])             // OK ボタン を配置
                    .asObservable()
                    .flatMap { Observable.just(($0, indexPath)) }
            } 　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　// flatMap で別の Observable へと変換 
　　　　　　　.flatMap { [unowned self] (action, index) -> Observable<Model> in
                switch action {
                case .Confirm:  // 入力が "OK" のとき
　　　　　　　　　 // Delete ボタンを押下したときの処理 ..
　　　　　　　　　 }
            }.subscribeNext { _ in return }
            .addDisposableTo(disposeBag)
```

ActionSheet を表示し・入力を Observable で返すメソッド(`promptFor`)の実装は次のとおりです: 

```
    func promptFor<Action : CustomStringConvertible>(alert: UIAlertController,
        cancelAction: Action, actions: [Action]) -> Observable<Action> {
        
        return Observable.create { [unowned self] observer in
            
            alert.addAction(UIAlertAction(title: cancelAction.description, style: .Cancel) { _ in
                observer.on(.Next(cancelAction))
            })
            
            for action in actions {
                alert.addAction(UIAlertAction(title: action.description, style: .Default) { _ in
                    observer.on(.Next(action))
                })
            }
            
            self.presentViewController(alert, animated: true, completion: nil)
            
            return AnonymousDisposable {
                alert.dismissViewControllerAnimated(false, completion: nil)
            }
        }
    }
```

なお、ActionSheet の入力結果は パターンマッチとして記述したかったので  enum (`Action` ) として実装しています: 

```
    enum Action: CustomStringConvertible {
        case Cancel(String)
        case Confirm(String)
        var description: String {
            switch self {
            case .Cancel(let title): return title
            case .Confirm(let title): return title
            }
        }
    }
```

# まとめ

RxSwift を使って UIAlertController に対する入力を Observable として返すメソッドを作ってみました。

RxCocoa で入力を Observable として扱っている場合や Observable を返す API をほかにも使っている場合は ActionSheet の入力も Observable として組み合わせられるので 見通しがよくなると思います。

# ソースコード

上記の `promptFor(:cancelAction:actions)` と テキストフィールド付きの `inputFor(:inputAction:cancelAction:)` のソースコードを gist に置いておきます。

> kumapo/UIViewController+AlertPresentable.swift
> https://gist.github.com/kumapo/1e2803dcb122538d6bfb