[SwiftUI] UIImagePickerController を SwiftUI から使う方法

SwiftUI

iPhone/iPad で写真を扱う時には、AVFoundation か UIImagePickerController を使う必要がありますが、UIImagePickerController を使うのが簡単です。

SwiftUI と組み合わせて、UIImagePickerController を使う方法を説明します。

注意 2020.12.7 追記
iOS14 以降では、UIImagePickerController の代わりに、PHPickerViewController の使用を勧められています

SwiftUI[SwiftUI] SwiftUI から PHPicker を使用する方法

Info.plist

最近の iOS アプリのお約束ですが、ユーザーデータ等にアクセスするためには、Info.plist にその理由を記載する必要があります。

カメラにアクセスするならば、以下のキー値とその理由を、Info.plist に追加しておく必要があります。

NSCameraUsageDescription
カメラを使う理由を記載

写真アルバムにアクセスするならば、NSPhotoLibraryAddUsageDescription も追加する必要があります。

UIViewControllerRepresentable

UIKit の ViewController を SwiftUI に組み込むときには、UIViewControllerRepresentable を使って、wrap します。

UIImagePickerController も UIViewControllerRepresentable を使って、以下のように wrap することができます。

@Binding の image はユーザーが、写真を撮ったり、選択したときに、返される UIImage です。showCameraView は、カメラ/写真ビューの表示制御フラグです。

内部クラス Controller を作って、Delegate に設定しています。ユーザー操作に応答するための機能を実装します。UIImagePickerControllerDelegate 相当です。

コード

struct SwiftUIImagePicker: UIViewControllerRepresentable {
    @Binding var image: UIImage?
    @Binding var showCameraView: Bool
    
    func makeCoordinator() -> Coordinator {
        return Coordinator(self)
    }
    
    func makeUIViewController(context: Context) -> UIImagePickerController {
        let viewController = UIImagePickerController()
        viewController.delegate = context.coordinator
        return viewController
    }
    
    func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {
        print("updateUIViewController is called")
    }
    
    class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
        let parent: SwiftUIImagePicker
        
        init(_ parent: SwiftUIImagePicker) {
            self.parent = parent
        }
        
        func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
            // user picked an image
            if let uiImage = info[.originalImage] as? UIImage {
                self.parent.image = uiImage
            }
            self.parent.showCameraView = false
        }
        
        func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
            // canceled
            self.parent.showCameraView = false
        }
    }
}

UIImagePickerControllerDelegate

ユーザーの操作に対して呼び出されるDelegateが、UIImagePickerControllerDelegate です。

以下の関数がポイントになります。

func imagePickerController(UIImagePickerController, didFinishPickingMediaWithInfo: [UIImagePickerController.InfoKey : Any])
ユーザーが撮影した写真に対して"これを使う"を選択したり、写真アプリから写真を選択すると呼び出されます。
func imagePickerControllerDidCancel(UIImagePickerController)
ユーザーが、キャンセルを選択すると呼び出されます。

例えば、ユーザーが写真を撮影したり、選択したときに、UIImage として保存するコードは以下のようになります。

コード

func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
  if let uiImage = info[.originalImage] as? UIImage {  // イメージを選択された?
    self.parent.image = uiImage                        // 選択されたら、内部変数に保存
  }
  self.parent.showCameraView = false // 選択後、元の画面に戻る
}

サンプルアプリ

仕様

画面のカメラボタンを押すと、カメラでの撮影ビューを開きます。

写真が撮影されたら、元の画面のカメラボタンの位置に、撮影された写真を表示します。

起動直後 (スナップショットは、iPad の Landscape ですが、特に意味はありません)

justafterlaunch

カメラボタン押下後

afterCameraButtonPushed

ここで、キャンセルが押されると Delegate の該当コールバック(DidCancel)が呼ばれます。撮影ボタンを押しても、Delegate は、何も呼び出されません。(UIImagePickerController の内部遷移が起こっています)

撮影ボタン押下後

afterCaptureButtonPushed

ここで、"Use Photo"ボタンが押されると、Delegate の該当コールバックが呼ばれます(didFinishPickngMediaWithInfo)。"Retake"ボタンは、再度撮影画面に戻りますが、Delegate は、何も呼び出されません。(UIImagePickerController の内部遷移が起こっています)

”Use Photo"ボタン押下後

appScreenAfterDone

選択された写真が、中央に表示されます。

サンプルの実装コードは以下のものです。

SwiftUIImagePicker.swift

//
//  SwiftUIImagePicker.swift
//  CameraAccess
//
//  Created by Tomoaki Yagishita on 2020/08/03.
//  Copyright © 2020 SmallDeskSoftware. All rights reserved.
//

import Foundation
import SwiftUI

struct SwiftUIImagePicker: UIViewControllerRepresentable {
    @Binding var image: UIImage?
    @Binding var showCameraView: Bool
    
    func makeCoordinator() -> Coordinator {
        return Coordinator(self)
    }
    
    func makeUIViewController(context: Context) -> UIImagePickerController {
        let viewController = UIImagePickerController()
        viewController.delegate = context.coordinator
        if UIImagePickerController.isSourceTypeAvailable(.camera) {
            viewController.sourceType = .camera
        }

        return viewController
    }
    
    func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {
        print("updateUIViewController is called")
    }
    
    class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
        let parent: SwiftUIImagePicker
        init(_ parent: SwiftUIImagePicker) {
            self.parent = parent
        }
        
        func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
            // user picked an image
            if let uiImage = info[.originalImage] as? UIImage {
                self.parent.image = uiImage
            }
            self.parent.showCameraView = false
        }
        
        func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
            self.parent.showCameraView = false
        }
    }
    
}
ContentView.swift

//
//  ContentView.swift
//  CameraAccess
//
//  Created by Tomoaki Yagishita on 2020/08/03.
//  Copyright © 2020 SmallDeskSoftware. All rights reserved.
//

import SwiftUI

struct ContentView: View {
    @State private var image: UIImage?
    @State private var showCameraView = false
    var body: some View {
        VStack {
            if image != nil {
                Image(uiImage: image!)
                    .resizable()
                    .scaledToFit()
            } else {
                Button(action: {
                    showCameraView.toggle()
                }, label: {
                    Image(systemName: "camera")
                        .resizable()
                        .scaledToFit()
                        .frame(width: 300, height: 300)
                })
            }
            Text("here is the place for options")
            .padding()
        }
        .sheet(isPresented: $showCameraView, content: {
            SwiftUIImagePicker(image: self.$image, showCameraView: self.$showCameraView)
        })
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

まとめ: UIImagePickerControllerの使い方

カメラビューを起動して、画像を選択するだけであれば、UIImagePickerController を使うのが非常に簡単です。SwiftUI からでも上記のように非常に簡単に取得することができます。

作ったSwiftUIImagePickerControllerは、こちらから。

説明は以上です。

SwiftUI おすすめ本

SwiftUI を理解するには、以下の本がおすすめです。

# SwiftUI2.0 が登場したことで少し古くなってしまいましたが、いまでも 定番本です。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です