LoginSignup
24

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-02-11

問題

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 のコードのなかにありました。

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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
24