目次
概要
自分で作ったアプリで写真アプリの画像を使うということは、ちゃんと画像を変数に格納する必要がある。
今回はその実装部分を記載する。
SwiftUIで写真アプリから画像を複数取得する処理はなかなか記載されていなかったのでメモ。
ソースコード
import SwiftUI
@main
struct TestApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
PhotoView()
}
}
}
import SwiftUI
struct PhotoView: View {
@ObservedObject private var viewModel = PhotoViewModel()
@State var selectedImages: [UIImage] = []
var body: some View {
VStack {
HStack {
Spacer()
Button {
viewModel.isShownPhotoList = true
} label: {
Text("フォトライブラリ表示")
}
Spacer()
Button {
selectedImages.removeAll()
} label: {
Text("クリア")
}
Spacer()
}
ScrollView {
ForEach(selectedImages, id: \.self) { image in
Image(uiImage: image)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: UIScreen.main.bounds.width)
}
}
}
.sheet(isPresented: $viewModel.isShownPhotoList) {
PhotoPickerView(selectedImages: $selectedImages)
}
}
}
import SwiftUI
import PhotosUI
struct PhotoPickerView: UIViewControllerRepresentable {
var configuration: PHPickerConfiguration
let completion: (([PHPickerResult]) -> Void)?
@Binding var selectedImages: [UIImage]
init(selectedImages: Binding<[UIImage]>, completion: (([PHPickerResult]) -> Void)? = nil) {
configuration = PHPickerConfiguration(photoLibrary: .shared())
configuration.filter = .images
configuration.preferredAssetRepresentationMode = .current
configuration.selectionLimit = 5
self.completion = completion
self._selectedImages = selectedImages
}
func makeUIViewController(context: Context) -> PHPickerViewController {
let controller = PHPickerViewController(configuration: configuration)
controller.delegate = context.coordinator
controller.isModalInPresentation = true
return controller
}
func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) {
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
}
class Coordinator: NSObject {
private var parent: PhotoPickerView
init(_ parent: PhotoPickerView) {
self.parent = parent
}
}
extension Coordinator: PHPickerViewControllerDelegate {
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
results.forEach { result in
let itemProvider = result.itemProvider
if itemProvider.canLoadObject(ofClass: UIImage.self) {
itemProvider.loadObject(ofClass: UIImage.self) { [weak self] image, error in
guard let self,
let image = image as? UIImage else {
return
}
self.parent.selectedImages.append(image)
}
}
}
picker.dismiss(animated: true)
}
}
デモ動画
詳細
PhotoView.swift
初期設定と選択可能な上限数の設定
まずは以下の部分で初期設定を行う。
一度にselectionLimitで選択できる数を5にしている。
configuration = PHPickerConfiguration(photoLibrary: .shared())
configuration.filter = .images
configuration.preferredAssetRepresentationMode = .current
configuration.selectionLimit = 5
選択した画像を変数に格納し、画面に反映させる
そして、以下の箇所で選んだ画像をselectedImagesに格納し、@Bindingで定義することでその格納した結果がPhotoViewのselectedImagesに反映されるようにしている。
@Binding var selectedImages: [UIImage]
init(selectedImages: Binding<[UIImage]>, completion: (([PHPickerResult]) -> Void)? = nil) {
// 中略
self._selectedImages = selectedImages
}
extension Coordinator: PHPickerViewControllerDelegate {
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
// 中略
guard let self,
let image = image as? UIImage else {
return
}
self.parent.selectedImages.append(image)
// 中略y
}
}
@Bindingに関しては別の記事で記載するが、とあるクラスで変更した内容を別のクラスの変数に反映させてViewを更新したい時は@Bindingで設定する。
しかし、それだけでは初期化に関するエラーが発生する。そのため、@Bindingの初期化は変数名の前に「_」をつけることで@Binding<T>として初期化することができる。
ちなみに呼び出す側はPhotoPickerView.swiftの項目で記載する。
選択した画像のデータをUIImageのオブジェクトに変換する
選択した画像のデータはPHPickerResult型になっている。それをUIImage型に変換するにはPHPickerResultのitemProviderを元に考える。
extension Coordinator: PHPickerViewControllerDelegate {
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
results.forEach { result in
let itemProvider = result.itemProvider
if itemProvider.canLoadObject(ofClass: UIImage.self) {
itemProvider.loadObject(ofClass: UIImage.self) { [weak self] image, error in
guard let self,
let image = image as? UIImage else {
return
}
self.parent.selectedImages.append(image)
}
}
}
picker.dismiss(animated: true)
}
}
以下のようにitemProviderがUIImageに変換できるかどうかを確認する。
itemProvider.canLoadObject(ofClass: UIImage.self)
そして、得られたUIImage型のオブジェクトを以下の処理を行う。
要はselectedImagesにUIImage型ののオブジェクトを追加する。
そして、追加されてselectedImagesが更新されたら、それが@Bindingの効果でPhotoPickerViewに反映される。
{ [weak self] image, error in
guard let self,
let image = image as? UIImage else {
return
}
self.parent.selectedImages.append(image)
}
PhotoPickerView.swift
まず、画像の一覧を表示する箇所は以下になる。
@State型の変数を@Bindingに格納する。こうすることで、PhotoPickerViewで変更された内容を画面に反映させることができる。
.sheet(isPresented: $viewModel.isShownPhotoList) {
PhotoPickerView(selectedImages: $selectedImages)
}
そして、上記でPhotoPickerViewで変更された内容を表示するために、以下のようにselectedImagesで繰り返し文を記載する。
ScrollView {
ForEach(selectedImages, id: \.self) { image in
Image(uiImage: image)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: UIScreen.main.bounds.width)
}
}
参考ページ
- Qiita「【SwiftUI】@Bindingを初期化する時の注意点」
- Qiita「[SwiftUI]for, ForEach, while, forEach の違いと使用例」
- すいすいSwift「SwiftUIでPHPickerViewControllerを使って画像を選択する」
- Qiita「iPhoneのフォトライブラリから複数写真を選択【PHPickerViewController】」