iOS
Swift

UIImagePickerController表示中にsourceTypeを切り替える

TL;DR

表示中のUIImagePickerControllersourceType変更はうまく動かない
→ カメラ起動、フォトライブラリ起動でボタンを分けて先にsourceType決めさせる
→ またはインスタンス再生成する

やりたいこと

カメラを起動して撮影した画像を取得したい。
また、オプションとしてフォトライブラリ内からも画像を選択できるようにしたい。

やったこと

実装方針

UIImagePickerControllerでカメラを起動
フォトライブラリボタンがタップされたらUIImagePickerControllersourceTypecameraからphotoLibraryに変更する

ソース

本来はUIImagePickerControllercameraOverlayViewを使用して
カメラ表示中にフォトライブラリボタンを設ける予定だが、
とりあえずテストのためpickerのキャンセルボタンでsourceType切り替えを実装

FirstViewController.swift
import UIKit
import Photos

class FirstViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate {

    var picker: UIImagePickerController!

    override func viewDidLoad() {
        super.viewDidLoad()
        // 許可リクエスト
        AVCaptureDevice.requestAccess(for: AVMediaType.video) { (Bool) in
            return
        }
    }

    /// Picker起動ボタン
    ///
    /// - Parameter sender:
    @IBAction func btn1_TouchUpInside(_ sender: Any) {
        pickerInit(type: UIImagePickerController.SourceType.camera)
    }

    /// Picker初期化
    ///
    /// - Parameter type: sourceType
    func pickerInit(type: UIImagePickerController.SourceType){
        self.picker = UIImagePickerController()
        self.picker.sourceType = type
        self.picker.delegate = self

        self.picker.navigationBar.tintColor = UIColor.white
        self.picker.navigationBar.barTintColor = UIColor.gray

        present(self.picker, animated: false, completion: nil)
    }

    // MARK: - UIImagePickerControllerのデリゲートメソッド

    /// 画像選択時
    ///
    /// - Parameters:
    ///   - picker:
    ///   - info:
    private func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
        // モーダルビューを閉じる
        self.picker.dismiss(animated: true, completion: nil)
    }

    /// キャンセル時
    ///
    /// - Parameter picker:
    func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
        // 表示中のpickerのsourceTypeを切り替えるだけ
        if picker.sourceType == UIImagePickerController.SourceType.photoLibrary {
            self.picker.sourceType = UIImagePickerController.SourceType.camera
        }
        else {
           self.picker.sourceType = UIImagePickerController.SourceType.photoLibrary
        }
    }
}

結果

スクショ

1.起動時
起動時.PNG

2.Picker起動(カメラ)
カメラ.png

3.sourceType切り替え(フォトライブラリ)
フォトライブラリ.PNG

4.sourceType切り替え(カメラ)
カメラ.png

5.sourceType切り替え(フォトライブラリ)
フォトライブラリ?.PNG

!!!?!?!!???!!?!??!?!???wwwwwwwww
カメラは普通なのにフォトライブラリ2回目表示すると何も出ない!なんやこれ!!

デバッグ

落ち着け…
俺には(最近覚えた)レイアウトデバッグがある…!

1.正常時

スクリーンショット 2018-10-12 12.51.44.png

2.異常時

スクリーンショット 2018-10-12 12.52.28.png

PUPhotoPickerHostViewControllerの霊圧が消えた…!?
ていうか誰だよコイツ!ググっても全然わかんねぇ!!
あっ…フレームワークさんちのお子さん…?
それじゃあ仕方ないわねぇウフフ

結局見てもよくわかりませんでした。

じゃあどうするか

一回作ったUIImagePickerControllerでコロコロsourceType変えてるのがまずいと予想。
写真撮影とフォトライブラリからの選択はボタン分けてるアプリばっかりだし。

実装方針

UIImagePickerControllerでカメラを起動
フォトライブラリボタンがタップされたら表示中のUIImagePickerControllersourceType変更ではなく、sourceTypeを変更したUIImagePickerControllerインスタンスを新規で生成する。

ソース

本来はカメラ表示中にフォトライブラリボタンを設ける予定だが以下略

SecondViewController.swift
import UIKit
import Photos

class SecondViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate{

    var picker: UIImagePickerController!

    override func viewDidLoad() {
        super.viewDidLoad()
        // 許可リクエスト
        AVCaptureDevice.requestAccess(for: AVMediaType.video) { (Bool) in
            return
        }
    }

    /// Picker起動ボタン
    ///
    /// - Parameter sender:
    @IBAction func btn1_TouchUpInside(_ sender: Any) {
        pickerInit(type: UIImagePickerController.SourceType.camera)
    }

    /// Picker初期化
    ///
    /// - Parameter type: sourceType
    func pickerInit(type: UIImagePickerController.SourceType){
        self.picker = UIImagePickerController()
        self.picker.sourceType = type
        self.picker.delegate = self

        self.picker.navigationBar.tintColor = UIColor.white
        self.picker.navigationBar.barTintColor = UIColor.gray

        present(self.picker, animated: false, completion: nil)
    }

    // MARK: - UIImagePickerControllerのデリゲートメソッド

    /// 画像選択時
    ///
    /// - Parameters:
    ///   - picker:
    ///   - info:
    private func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {

        // モーダルビューを閉じる
        self.picker.dismiss(animated: true, completion: nil)
    }

    /// キャンセル時
    ///
    /// - Parameter picker:
    func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
        // 一旦表示中のモーダルを閉じる
        self.picker.dismiss(animated: false, completion: nil)

        // sourceTypeを切り替えて再度インスタンス生成から行う
        if picker.sourceType == UIImagePickerController.SourceType.photoLibrary {
            pickerInit(type: UIImagePickerController.SourceType.camera)
        }
        else {
            pickerInit(type: UIImagePickerController.SourceType.photoLibrary)
        }
    }
}

結果

何度切り替えを行なっても正常にカメラ、フォトライブラリが表示されるようになりました。

まとめ

ぶっちゃけ要因はよくわかりませんでした。
表示中のUIImagePickerControllersourceType変更はガイドラインに沿ってないのかなぁ…?
一旦このやり方で解決はしたけど、もっと楽な方法とかありそう。