はじめに
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()
}
}
}
ここでのCoordinator
はUIImagePickerControllerDelegate
とUINavigationControllerDelegate
に準拠する必要があリます。
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
でインラインのフォトピッカーが簡単に実装できそうなので、この辺りも調べていきたいと思います。
参考