1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[SwiftUI] カメラorライブラリから写真を取り込む

Last updated at Posted at 2024-09-02

はじめに

SwiftUIで作った画面に写真をカメラorライブラリから一枚撮影or選択する機能を実装したく調べました。

環境

Xcode 15.4
iOS15 ~

内容

カメラの表示

カメラを動かすだけであればUIImagePickerControllerを使う方法が一番簡単そうでした。
SwiftUIで扱うため、UIViewControllerRepresentableを使います。

import SwiftUI

struct CameraCaptureView: UIViewControllerRepresentable {
    @Binding var image: UIImage?
    @Environment(\.presentationMode) private var presentationMode

    func makeUIViewController(context: Context) -> UIImagePickerController {
        let picker = UIImagePickerController()
        picker.sourceType = .camera
        picker.delegate = context.coordinator
        picker.allowsEditing = true
        return picker
    }

    func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) { }

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
        let parent: CameraCaptureView

        init(_ parent: CameraCaptureView) {
            self.parent = parent
        }

        func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
            if let uiImage = info[.originalImage] as? UIImage {
                parent.image = uiImage
            }

            parent.presentationMode.wrappedValue.dismiss()
        }

        func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
            parent.presentationMode.wrappedValue.dismiss()
        }
    }
}

ここでのCoordinatorUIImagePickerControllerDelegateUINavigationControllerDelegateに準拠する必要があリます。

UIImagePickerController
weak open var delegate: (any UIImagePickerControllerDelegate & UINavigationControllerDelegate)?

ライブラリの表示

ライブラリを表示するため、PHPickerViewControllerを使います。(iOS16以上であればPhotosPickerが使えます)
カメラ同様UIImagePickerControllerを使っても表示することはできますが、こちらはすでに非推奨となっております。
こちらもSwiftUIで扱えるようにUIViewControllerRepresentableを使います。

ちなみにUIImagePickerControllerからPHPickerViewControllerにすることで以下のような改良されているようです。

  • 遅延画像読み込みとリカバリーUI
  • RAW画像やパノラマ画像などの大型で複雑なアセットの確実な処理
  • UIImagePickerControllerでは利用できないユーザー選択可能なアセット
  • Live Photosのみを表示するようにピッカーを構成可能
  • ライブラリアクセスなしでPHLivePhotoオブジェクトを利用可能
  • 無効な入力に対するより厳密な検証
  • (オプションメニューはPHPickerViewControllerのみ表示される)
import SwiftUI
import PhotosUI

struct PhotoLibraryPickerView: UIViewControllerRepresentable {
    @Binding var image: UIImage?
    @Environment(\.presentationMode) private var presentationMode

    func makeUIViewController(context: Context) -> PHPickerViewController {
        var config = PHPickerConfiguration()
        config.filter = .images
        let picker = PHPickerViewController(configuration: config)
        picker.delegate = context.coordinator
        return picker
    }

    func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) {}

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    class Coordinator: NSObject, PHPickerViewControllerDelegate {
        let parent: PhotoLibraryPickerView

        init(_ parent: PhotoLibraryPickerView) {
            self.parent = parent
        }

        func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
            parent.presentationMode.wrappedValue.dismiss()

            guard let provider = results.first?.itemProvider else { return }

            if provider.canLoadObject(ofClass: UIImage.self) {
                provider.loadObject(ofClass: UIImage.self) { image, _ in
                    DispatchQueue.main.async {
                        self.parent.image = image as? UIImage
                    }
                }
            }
        }
    }
}

カメラ・ライブラリから写真を読み込む

カメラとライブラリのオプションを表示して、写真を取り込む画面の最小実装をしてみました。
カメラの表示はフルスクリーンにするようにドキュメントに記載がありましたので、fullScreenCoverを使います。

.fullScreenCover(isPresented: $showCamera) {
    CameraCaptureView(image: $image)
        .ignoresSafeArea()
}

import SwiftUI

struct PhotoPickerView: View {
    @State var image: UIImage?
    @State private var showImagePickerDialog = false
    @State private var showCamera: Bool = false
    @State private var showLibrary: Bool = false

    var body: some View {
        VStack(spacing: 20) {
            Text("写真を追加")

            if let image {
                Image(uiImage: image)
                    .resizable()
                    .aspectRatio(contentMode: .fit)

                Button("削除する") {
                    self.image = nil
                }
            } else {
                Button("選択する") {
                    showImagePickerDialog = true
                }
            }
        }
        .fullScreenCover(isPresented: $showCamera) {
            CameraCaptureView(image: $image)
                .ignoresSafeArea()
        }
        .sheet(isPresented: $showLibrary, content: {
            PhotoLibraryPickerView(image: $image)
                .ignoresSafeArea()
        })
        .confirmationDialog(
            "",
            isPresented: $showImagePickerDialog,
            titleVisibility: .hidden
        ) {
            Button {
                showCamera = true
            } label: {
                Text("カメラで撮る")
            }
            Button {
                showLibrary = true
            } label: {
                Text("アルバムから選ぶ")
            }
            Button("キャンセル", role: .cancel) {
                showImagePickerDialog = false
            }
        }
    }
}

おわりに

iOSのサポートバージョン次第では不要な実装も含まれているかと思います。
iOS17以上であればPhotosPickerでインラインのフォトピッカーが簡単に実装できそうなので、この辺りも調べていきたいと思います。

参考

1
1
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
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?