RxSwiftのExampleからのコピー
3ファイルをコピーしておきます。
- RxImagePickerDelegateProxy.swift
- UIImagePickerController+Rx.swift
- UIImagePickerController+RxCreate.swift
AppDelegateへの記述追加
上記3ファイルを利用する場合には忘れないように。
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
// for ImagePicker この1行を追加
RxImagePickerDelegateProxy.register { RxImagePickerDelegateProxy(imagePicker: $0) }
return true
}
アクションシートのObsrvable化
よくあるあれ
import UIKit
import RxSwift
import RxCocoa
// アクションシートの項目を指定する構造体
struct ActionSheetItem<Type> {
let title: String
let selectType: Type
let style: UIAlertActionStyle
}
extension UIAlertController {
// アクションシートに項目を追加し、Observable化
func addAction<T>(actions: [ActionSheetItem<T>], cancelMessage: String, cancelAction: ((UIAlertAction) -> Void)?) -> Observable<T> {
return Observable.create { [weak self] observer in
actions.map { action in
return UIAlertAction(title: action.title, style: action.style) { _ in
observer.onNext(action.selectType)
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)
}
}
}
}
選択項目に色々使えるようにジェネリック型で宣言しておきます。
UIViewControllerからアクションシートを呼ぶ
上記で作成したアクションシートをUIViewControllerから呼べるようにします。
import UIKit
import RxSwift
import RxCocoa
extension UIViewController {
// Observable化したアクションシートの表示
func showActionSheet<T>(title: String?, message: String?, cancelMessage: String = "キャンセル", actions: [ActionSheetItem<T>]) -> Observable<T> {
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)
})
}
}
アクションシートの選択項目を追加した後に、presentを実行して表示します。
当然ですが、項目を選択した後に、selectTypeの内容が.nextで渡されるようにします。
ViewModel作成
RxSwiftのサンプルをちょっと真似してみます。Wireframeを利用します。
import Foundation
import RxSwift
import RxCocoa
class SampleViewModel {
// Inputs
typealias Inputs = (
Signal<Void>
)
typealias Wireframe = (
SampleWireframeType
)
// Outputs
let selectedImage: BehaviorRelay<UIImage?>
private let disposeBag = DisposeBag()
init(inputs: Inputs, wireframe: Wireframe) {
self.selectedImage = wireframe.selectedImage
// アクションシート表示
inputs.emit(onNext: { _ in
// アクションシート選択項目
var actions = [ActionSheetItem<UIImagePickerControllerSourceType>(
title: "ライブラリから選択",
selectType: UIImagePickerControllerSourceType.photoLibrary,
style: .default)]
if UIImagePickerController.isSourceTypeAvailable(.camera) {
actions.insert(ActionSheetItem<UIImagePickerControllerSourceType>(
title: "カメラを起動",
selectType: UIImagePickerControllerSourceType.camera,
style: .default), at: 0)
}
wireframe.shwoActionSheet(title: "画像設定", message: "選択してください。", actions: actions)
})
.disposed(by: self.disposeBag)
}
}
ボタンを押したときに、UIImagePickerControllerSourceTypeごとに選択項目を持ったアクションシートを表示します。selectTypeに利用するUIImagePickerControllerSourceTypeを指定します。
Wireframe作成
RxSwiftサンプルを参考に、、、
import UIKit
import RxSwift
import RxCocoa
protocol SampleWireframeType {
func shwoActionSheet<T>(title: String, message: String, actions: [ActionSheetItem<T>])
var selectedImage: BehaviorRelay<UIImage?> { get }
}
class SampleWireframe: SampleWireframeType {
var viewController: UIViewController? { return sampleViewController }
private weak var sampleViewController: SampleViewController?
private let disposeBag = DisposeBag()
let selectedImage: BehaviorRelay<UIImage?>
init(view: SampleViewController) {
self.sampleViewController = view
self.selectedImage = BehaviorRelay<UIImage?>(value: nil)
}
// アクションシートを起動してUIImagePickerController起動
func shwoActionSheet<T>(title: String, message: String, actions: [ActionSheetItem<T>]) {
viewController?.showActionSheet(title: title, message: message, actions: actions)
.subscribe({ [unowned self] event in
if let sourceType = event.element as? UIImagePickerController.SourceType {
switch sourceType {
case .camera:
self.launchPhotoPicker(.camera)
case .photoLibrary:
self.launchPhotoPicker(.photoLibrary)
case .savedPhotosAlbum:
break
}
}
})
.disposed(by: self.disposeBag)
}
// UIImagePickerControllerの起動と選択した画像の処理
private func launchPhotoPicker(_ type: UIImagePickerController.SourceType) {
UIImagePickerController.rx.createWithParent(self.viewController) { picker in
picker.sourceType = type
picker.allowsEditing = true
}
.flatMap { $0.rx.didFinishPickingMediaWithInfo }
.take(1)
.map { info in return info[UIImagePickerControllerOriginalImage] as? UIImage }
.bind(to: self.selectedImage)
.disposed(by: self.disposeBag)
}
}
アクションシートの項目を選択した後に、subscribeで選択したselectTypeが得られます。そのままUIImagePickerControllerを起動して選択した画像の処理も行います。
switchなくてもいいけど、、、ここで分岐できるという目印のために、、、
ViewControllerの作成
作ったものを利用する例。
import UIKit
import RxSwift
import RxCocoa
class SampleViewController: UIViewController {
@IBOutlet weak var imageView: UIImageView!
@IBOutlet weak var button: UIButton!
private var viewModel: SampleViewModel!
private let disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
// ViewModel初期化
viewModel = SampleViewModel(
inputs: button.rx.tap.asSignal(),
wireframe: SampleWireframe(view: self)
)
// 画像選択
viewModel.selectedImage
.asObservable()
.bind(to: self.imageView.rx.image)
.disposed(by: self.disposeBag)
}
}