iOS で RxSwift を使ってカメラかフォトライブラリから画像を一枚取得する

# やったこと
iOS でフォトライブラリ or カメラから画像を一枚取得する機能を`UIImagePickerController`と RxSwift を使って実装したので晒してみます。複数箇所で楽に使えることを目指しました。

### 環境
Swift 3.1

# 処理のだいたいの流れ

1. フォトライブラリ or カメラへのアクセス許可を確認。なければ求める
1. 許可があれば`UIImagePickerController`をモーダル表示
1. 画像が選択されたら`UIImagePickerController`を閉じて画像を`UIImage`で取得


# 実装

* `UIImagePickerController`表示 -> 画像選択 -> `UIImagePickerController`を閉じるまでを行ってくれるクラス（`ImageManager`）を作成し、使う側は`pick(on:sourceType:)`を呼ぶこととしました。
* `UIImagePickerController`の画像取得イベントは delegate として渡す`ImagePickerControllerDelegate`クラスに`PublishSubject`を持たせることで画像の取得をストリームとして受け取ります。
* アクセス権限確認まわりのソースが長くなって見にくかったのでメソッドを分けています。
* 画像の取得に失敗した際には独自に定義した`InternalError`クラスからエラー内容を把握します。`InternalError`の実装は割愛しますが`Error`に準拠したただの Enum です。
* アクセス不許可等でストリームにエラーが流れた際にストリームが止まってしまうので、エラーを流さず`Result`型を使うことでそれを回避しました。`.catchError`を使用側で使うことも考えましたがこちらのほうが使用側のソースは短く済みそうです。

`Result`型には [antitypical/Result](https://github.com/antitypical/Result) を使用しました。

```swift:ImageManager.swift
final class ImageManager {
	
    // 権限確認 -> 選択された画像のストリーム
    func pick(on viewController: UIViewController, sourceType: UIImagePickerControllerSourceType) -> Observable<Result<UIImage, InternalError>> {
        return self.authorizedSourceType(sourceType)
            // 画像選択画面作成
            .map { sourceType -> (picker: UIImagePickerController, delegate: ImagePickerControllerDelegate) in
            // UIImagePickerControllerとそれにわたすdelegateを生成
                let picker = UIImagePickerController()
                let delegate = ImagePickerControllerDelegate()
                picker.delegate = delegate
                picker.allowsEditing = false
                picker.sourceType = sourceType
                return (picker, delegate)
            }
            .subscribeOn(MainScheduler.instance)
            // 表示
            .do(onNext: { [weak viewController] (picker, _) in 
            	viewController?.present(picker, animated: true) 
            })
            // 選択された画像のストリームをResultで
            .flatMap { (picker, delegate) -> Observable<Result<UIImage, InternalError>> in
                return delegate.pickedResultSubject
                    .do(onNext: { _ in picker.dismiss(animated: true) })
                    .map { result -> Result<UIImage, InternalError> in
                        switch result {
                        case .success(let image):
                            return .success(image)
                        case .failure(let error):
                            return .failure(error)
                        }
                }
            }
        
    }
    
    // 承認された画像ソースタイプのストリーム
    // 長いので別ストリームとして切り出した
    private func authorizedSourceType(_ sourceType: UIImagePickerControllerSourceType) -> Observable<UIImagePickerControllerSourceType> {
        return Observable<UIImagePickerControllerSourceType>.create { observer -> Disposable in
            switch sourceType {
            case .photoLibrary:
				// フォトライブラリ
                let status = PHPhotoLibrary.authorizationStatus()
                switch status {
                case .authorized:
                    // 承認されていればそのまま返す
                    observer.onNext(.photoLibrary)
                    observer.onCompleted()
                case .denied:
                    // 拒否
                    observer.onError(InternalError.photoLibraryAccessDenied)
                case .notDetermined:
                    // 未承認 許可を求める
                    PHPhotoLibrary.requestAuthorization { status in
                        if .authorized == status {
                            observer.onNext(.photoLibrary)
                            observer.onCompleted()
                        } else {
                            observer.onError(InternalError.photoLibraryAccessDenied)
                        }
                    }
                case .restricted:
                    // アクセス許可制限
                    observer.onError(InternalError.photoLibraryAccessDenied)
                }
            case .camera:
				// カメラ
                let status = AVCaptureDevice.authorizationStatus(forMediaType: AVMediaTypeVideo)
                switch status {
                case .authorized:
                    // 承認
                    observer.onNext(.camera)
                    observer.onCompleted()
                case .denied:
                    // 拒否
                    observer.onError(InternalError.cameraAccessDenied)
                case .notDetermined:
                    // 未承認 許可を求める
                    AVCaptureDevice.requestAccess(forMediaType: AVMediaTypeVideo) { authorized in
                        if authorized {
                            observer.onNext(.camera)
                            observer.onCompleted()
                        } else {
                            observer.onError(InternalError.cameraAccessDenied)
                        }
                    }
                case .restricted:
                    // アクセス許可制限
                    observer.onError(InternalError.cameraAccessDenied)
                }
            case .savedPhotosAlbum:
                // 実装しなかったので割愛。 .photoLibrary と同じようにできるはず
                break
            }
            
            return Disposables.create {
                observer.onCompleted()
            }
        }
    }
}

// UIImagePickerControllerに渡すデリゲート
fileprivate final class ImagePickerControllerDelegate: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
    
    // 選択された画像のストリーム
    let pickedResultSubject = PublishSubject<UIImage>()
    
    // MARK: - UIImagePickerControllerDelegate
    
    // 画像が選択された
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
        // 自身のsubjectにイベントを流す
        if let image = info[UIImagePickerControllerOriginalImage] as? UIImage {
            self.pickedResultSubject.onNext(image)
        } else {
            self.pickedResultSubject.onError(InternalError.couldNotPickImage)
        }
    }
    
}

```

## いざ使ってみる

ボタンのタップで`UIImagePickerController`に渡す`UIImagePickerControllerSourceType`を取得してます。

```swift:SomeViewController.swift

// ImageManagerを初期化しておく
private let imageManager = ImageManager()
private let bag = DisposeBag()

override func viewDidLoad() {
	super.viewDidLoad()
	
	// UIImagePickerControllerSourceTypeを取得
	let sourceTypeStream = Observable<UIImagePickerControllerSourceType>.of(
		self.cameraButton.rx.tap.asObservable().map { .camera }, 
		self.photoLibraryButton.rx.tap.asObservable().map { .photoLibrary }
		)
	   .merge()
   
	sourceTypeStream
		.flatMap { [unowned self] -> Observable<Result<UIImage, InternalError>>
			self.imageManager.pick(on: self, sourceType: sourceType)
		}
		.subscribe(onNext: { [weak self] result in
			switch result {
				case .success(let image): // 画像を取得できた場合の処理
				case .failure(let error): // アクセス不許可等、画像が取得できなかった場合の処理
			}
		})
		.disposed(by: self.bag)
}

```

## さいごに
ソースは割と長くなりましたが、複数箇所で楽に使えるようにはなったかなという感じです。もっと良い実装がありましたら教えてください（特にストリームが止まるのを回避するあたり）。