アクションシートのクロージャをObservable化して処理を一本化する
アクションシートってよくつかわれるんですかね? 私が携わっているプロジェクトではそこそこ使われています。
コレです
アクションシートの基本的な使い方
let actionSheet = UIAlertController(title:"Title", message: "Message", preferredStyle: .actionSheet) let action1 = UIAlertAction(title: "Action 1", style: .default) { (action: UIAlertAction!) in print("Selected Action!") } // キャンセル let cancel = UIAlertAction(title: "Cancel", style: .cancel) { (action: UIAlertAction!) in print("Selected Cancel!") } actionSheet.addAction(action1) actionSheet.addAction(cancel) present(alert, animated: true, completion: nil)
- UIAlertControllerをつくる
- アクションを追加する
- 表示する
これが基本的な使い方です。
また、UIAlertActionには選択されたときのクロージャが用意されていて、都度、処理を定義する必要があります。
パッとみて感じるのが、繰り返しの記述ということです。 こういうのをループにしたくなるのがプログラマの性ですよね。
ここで私は2つ思い浮かびました
- UIAlertActionの生成を共通化できないか?
- 「どれが選択されたか」をストリームとして扱えないか?
ということで、Rxをつかってコレを実現したいと思います。
アクションシートの特徴
簡単に述べると下記の3点
- アクションシート自体はタイトルとメッセージを持つ
- アクション毎にタイトルと選択された時の処理を持つ
- キャンセルは例外
選択をObservable化するにあたっての方針
- アクションの生成元となる情報の配列を引数に取るメソッドを定義し、
UIAlertAction
の生成をまとめる。 - どのアクションが選択されたかをenumで表現する
- アクションシートの起動はUIViewControllerのextensionにまかせてします
実装
早速実装です。 なお、Githubにもソースを上げてます
ActionSheetAction.swift
まずは、アクションシートの各行を表現する構造体を定義します
// MARK: - Action internal struct ActionSheetAction<Type: Equatable> { internal let title: String internal let actionType: Type internal let style: UIAlertActionStyle }
ジェネリクス(Type
)を用いることによって外からアクションのタイプ(enum)をインジェクトできるようにします。
この型は最終的にObservableに用います。
UIViewController+RxActionSheet.swift
次に、UIViewControllerにUIAlertControllerを表示するextensionを定義します
import UIKit import RxSwift internal extension UIViewController { internal func showActionSheet<Type>(title: String?, message: String? = nil, cancelMessage: String = "Cancel", actions: [ActionSheetAction<Type>]) -> Observable<Type> { let actionSheet = UIAlertController(title: title, message: message, preferredStyle: .actionSheet) return actionSheet.addAction(actions: actions, cancelMessage: cancelMessage, cancelAction: nil) .do(onSubscribed: { [weak self] in self?.present(actionSheet, animated: true, completion: nil) }) } }
サブスクライブされた時にアクションシートを表示しています
UIAlertController+RxActionSheet.swift
最後に、UIAlertControllerにアクション追加と同時にObservable化して返すextensionを実装します
import UIKit import RxSwift internal extension UIAlertController { internal func addAction<Type>(actions: [ActionSheetAction<Type>], cancelMessage: String, cancelAction: ((UIAlertAction) -> Void)? = nil) -> Observable<Type> { return Observable.create { [weak self] observer in actions.map { action in return UIAlertAction(title: action.title, style: action.style) { _ in observer.onNext(action.actionType) observer.onCompleted() } }.forEach { action in self?.addAction(action) } self?.addAction(UIAlertAction(title: cancelMessage, style: .cancel) { cancelAction?($0) observer.onCompleted() }) return Disposables.create { self?.dismiss(animated: true, completion: nil) } } } }
こんな感じでObservableを作成し、各アクションのクロージャでactionTypeとともにonNextを発行してあげることで
アクションシートの選択をストリーム化することができました。
使い方
AnimalSelectAction.swift
まずアクションの一覧をenumで定義します
enum AnimalSelectAction: String { case dog, cat, rabbit, panda static var AnimalSelectActions: [AnimalSelectAction] { return [.dog, .cat, .rabbit, .panda] } }
ViewController.swift
import UIKit import RxSwift final class ViewController: UIViewController { private let disposeBag = DisposeBag() @IBOutlet weak var showActionSheetButton: UIButton! override func viewDidLoad() { super.viewDidLoad() showActionSheetButton.rx .tap .asDriver() .drive(onNext: { [weak self] _ in self?.showActionSheet() }).disposed(by: disposeBag) } } private extension ViewController { func showActionSheet() { let actions = AnimalSelectAction.AnimalSelectActions .map { return ActionSheetAction(title: $0.rawValue, actionType: $0, style: .default) } // アクションシートを表示し、返ってくるObservableをSubscribeしておく。 showActionSheet(title: "Which Do you like?", actions: actions) .subscribe { (event: Event<AnimalSelectAction>) in NSLog(event.debugDescription) }.disposed(by: disposeBag) } }
動作
まとめ
ということで、ViewController側ではアクションシートの選択肢の列挙とそれに対して選択された時の記述をするだけで良くなりました。
UIAlertControllerもほぼ意識せずにできてるのはメリデメあるかもですが記述が簡潔になったかと思います。
extension最高\(^o^)/