問題
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> {
これを 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