写真アルバムからTransferable
によって画像を取得 (iOS)
Transferable
に準拠した画像クラスを以下のように設計しました。自分のアプリできちんと動くように、適宜修正してください。
final class ImageTranseferable: Transferable, Sendable {
let url: URL
init(url: URL) {
self.url = url
}
static var transferRepresentation: some TransferRepresentation {
FileRepresentation(importedContentType: .image) { receivedTransferrable in
if receivedTransferrable.isOriginalFile {
return ImageTranseferable(url: receivedTransferrable.file)
} else {
let copy = URL.temporaryDirectory.appending(path: "\(UUID().uuidString).\(receivedTransferrable.file.pathExtension)")
try? FileManager.default.copyItem(at: receivedTransferrable.file, to: copy)
return ImageTranseferable(url: copy)
}
}
}
}
FileRepresentation
を選択した理由は、appleのMeet Transferableで紹介されている下記選択基準によって決めました。
画像(UIImage
)はCodable
に準拠しておらず、オンメモリ上に存在するわけでもないので、FileRepresentation
を選択しました。
これを使って、UIKit
とSwiftUI
の両方で写真を読み込むようにしていきます。
UIKit
PHPickerViewControllerDelegate
で取得したデータをTransferable
によってアプリで使えるように
extension ViewController: PHPickerViewControllerDelegate {
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
let itemProvider = results.first!.itemProvider
if itemProvider.hasRepresentationConforming(toTypeIdentifier: UTType.image.identifier) {
let _ = itemProvider.loadTransferable(type: ImageTranseferable.self, completionHandler: {
result in
switch result {
case .success(let success):
guard let data = try? Data(contentsOf: success.url), let image = UIImage(data: data) else {
return
}
// 何かする。
case .failure(let failure):
// 何かする。
}
})
}
picker.dismiss(animated: true)
}
}
SwiftUI
photosPicker
で取得したデータをTransferable
によってアプリで使えるように
struct ContentView: View {
@State var isPresented: Bool = false
@State var items: [PhotosPickerItem] = [] {
didSet {
Task {
var _medias: [Media] = []
for item in items {
let imageRepr = try? await item.loadTransferable(type: ImageTranseferable.self)
if let imageRepr, let data = try? Data(contentsOf: imageRepr.url), let image = UIImage(data: data) {
_medias.append(.init(id: UUID().uuidString, image: image))
}
}
self.medias = _medias
}
}
}
@State var medias: [Media] = []
var body: some View {
ScrollView {
VStack {
Button(action: {
isPresented = !isPresented
}, label: {
Text("写真を開く")
})
ForEach(medias) {
media in
Image(uiImage: media.image!)
.resizable()
.scaledToFit()
}
}
}
.photosPicker(isPresented: $isPresented, selection: .init(get: {
self.items
}, set: {
medias in
self.items = medias
}), matching: .any(of: [.images]), photoLibrary: .shared())
}
}
extension ContentView {
struct Media: Identifiable {
let id: String
let image: UIImage?
}
}
終わりに
めっちゃ書き殴っているので、そのまま自分のアプリに使い回すのはやめてください🙇