LoginSignup
6
4

More than 5 years have passed since last update.

ActionSheet, UIImagePickerのRxSwift実装

Last updated at Posted at 2018-11-22

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)

    }
}

サンプル

6
4
0

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
6
4