Lento con forza

大学生気分のIT系エンジニアが色々書いてく何か。ブログ名決めました。

SwiftUIでUIImagePickerControllerのカメラを使いたい

SwiftUIでカメラを扱いたくなって色々調べた&試したので記事にします。

SwiftUIでカメラを扱いたい!

SwiftUIでカメラを直接扱うことは現在のところできず、UIKitで作ったカメラのビューを表示する必要があります。 カメラを使う一番簡単な方法は、UIImagePickerControllerを使うことです。これは画像を扱う一番簡単な方法で、カメラやメディアにアクセスするためのUIを持っています。使う側はDelegate経由で画像を受け取ることができます。

これはUIViewControllerのサブクラスなので、UIViewControllerRepresentableのサブクラスを定義し、UIImagePickerControllerを返すことでSwiftUIで扱えるようになると思ったので、以下のようなコードで試してみました。

struct ImagePickerWrapper : UIViewControllerRepresentable {
    typealias UIViewControllerType = UIViewController

    func makeUIViewController(context: UIViewControllerRepresentableContext<CardCaptureViewControllerWrapper>) -> CardCaptureViewControllerWrapper.UIViewControllerType {
        let imagePicker = UIImagePickerController()
        imagePicker.sourceType = .camera
        imagePicker.modalPresentationStyle = .fullScreen

        return imagePicker
    }

    func updateUIViewController(_ uiViewController: CardCaptureViewControllerWrapper.UIViewControllerType, context: UIViewControllerRepresentableContext<CardCaptureViewControllerWrapper>) {
    }
}

一見これで動作しているようですが、UIViewControllerRepresentableを経由したせいか、横画面で崩れてしまいました。

f:id:kouki_dan:20200531194510p:plain

また、この方法ではフルスクリーンのモーダルにすることができず、UIKitでUIImagePickerControllerを使った場合とは少し表示が変わってしまいます。

SwiftUIでView Controllerにアクセスする

UIViewControllerRepresentableを使う方法だとうまくいかなかったので、SwiftUIからUKitに直接アクセスする方法で解決しました。以下のように、独自のEnvironmentを定義し、任意のSwiftUIのViewからView Controllerを取得できるようにします。

struct ViewControllerKey: EnvironmentKey {
    static var defaultValue: UIViewController? {
        return  UIApplication.shared.windows.first?.rootViewController
    }
}

extension EnvironmentValues {
    var viewController: UIViewController? {
        get { return self[ViewControllerKey.self] }
        set { self[ViewControllerKey.self] = newValue }
    }
}

これを定義しておくことで、任意のViewからView Controllerにアクセスできます。

struct ContentView: View {
    @Environment(\.viewController)
    var viewController

    var body: some View {
        return Text("Hello World!")
    }
}

あとは、ボタンなどでUIImagePickerControllerのインスタンスを生成し、表示すればカメラを使うことができます。

struct ContentView: View {
    @Environment(\.viewController)
    var viewController

    var body: some View {
        return Text("Hello World!")
    }
}

f:id:kouki_dan:20200531200648j:plain

これでSwiftUIからUIImagePickerControllerを呼び出すことができました!

もっと良いやり方があったら教えてください!