🏙️
[SwiftUI]iOS 16から使えるPhotosPickerの使い方
PhotosPicker
PhotosPickerはUIKitで使用していたPHPickerViewController, UIImagePickerControllerのSwiftUI製のもので、iOS
, macOS
, watchOS
というマルチプラットフォーム対応しているPhoto Pickerです。
新しい要素
- Transferable
ドラッグ&ドロップができることを示す新しいプロトコルです。 - PhotosPickerItem
ユーザーがピックした後に、渡されるアイテムです。これを使ってアプリ側で写真/動画をロードします。
PHPickerViewControllerでいうPHPickerResult, NSItemProviderのようなものです。
PhotosPicker init
PhotosPickerのinitは以下のような形です。
struct ContentView: View {
@State var photoPickerItems: [PhotosPickerItem] = []
var body: some View {
PhotosPicker(
selection: $photoPickerItems, // Bindingした[PhotosPickerItem]
maxSelectionCount: 0, // 選択する写真の数(0で無制限)
selectionBehavior: .ordered, // 順番が関係するか
matching: .images, // 写真の種類を選択(nilでどれでも可に)
preferredItemEncoding: .current, // エンコードの種類(基本currentでいいはず)
photoLibrary: .shared()) { // ライブラリの選択
Image(systemName: "photo")
}
}
}
Load Photo
Task {
let image = try await photoPickerItem.loadTransferable(type: Image.self)
print(image)
}
画像 Pick Sample
struct ContentView: View {
@State var photoPickerItems: [PhotosPickerItem] = []
@State var images: [UIImage] = []
var body: some View {
VStack {
PhotosPicker(
selection: $photoPickerItems,
maxSelectionCount: 0,
selectionBehavior: .ordered,
matching: .images, // 写真の種類を画像(images)だけに
preferredItemEncoding: .current,
photoLibrary: .shared()) {
Image(systemName: "photo")
}
.onChange(of: photoPickerItems) { newPhotoPickerItems in
Task {
do {
for photoPickerItem in newPhotoPickerItems {
if let data = try await photoPickerItem.loadTransferable(type: Data.self) {
if let uiImage = UIImage(data: data) {
images.append(uiImage)
}
}
}
} catch {
print(error)
}
}
}
if !images.isEmpty {
TabView {
ForEach(images, id: \.self) { image in
Image(uiImage: image)
.resizable()
.scaledToFit()
}
}
.tabViewStyle(.page(indexDisplayMode: .always))
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(.black)
}
}
動画をPickしたい
動画をPickしたい場合新しくTransferableに準拠したものが必要です。
Transferable filerepresentation(Apple Documentation)
// サンプル実装
import CoreTransferable
struct Movie: Transferable {
let url: URL
static var transferRepresentation: some TransferRepresentation {
FileRepresentation(contentType: .movie) { movie in
SentTransferredFile(movie.url)
} importing: { receivedData in
let fileName = receivedData.file.lastPathComponent
let copy: URL = FileManager.default.temporaryDirectory.appendingPathComponent(fileName)
if FileManager.default.fileExists(atPath: copy.path) {
try FileManager.default.removeItem(at: copy)
}
try FileManager.default.copyItem(at: receivedData.file, to: copy)
return .init(url: copy)
}
}
}
.onChange(of: photoPickerItems) { newPhotoPickerItems in
Task {
do {
for photoPickerItem in newPhotoPickerItems {
if let movie = try await photoPickerItem.loadTransferable(type: Movie.self) {
movies.append(movie)
}
}
} catch {
print(error)
}
}
}
Sample Project
iOS/ macOSで使えるSample Projectを作成しました。
watchOSはiOS/ macOSとは少し異なる挙動で、実機がないとテストができないため、対応していません。
Discussion