Sponsor Link
UIImagePickerController
WWDC2020 の“Meet the new Photos picker”をみると、UIImagePickerController を PHPicker に切り替えることを勧めているようです。
ドキュメントを確認しても、まだ、Decprecated にはなっていませんが、強い理由がなければ、今後の開発には PHPicker を使用する方が良いようです。
UIImagePickerController の定義を確認すると複数の場所に以下のような記載があります。
@available(iOS, introduced: 11.0, deprecated: 100000, message: "Will be removed in a future release, use PHPicker.")
# 11.0 は付与されているプロパティやメソッドにより異なります
deprecated になるバージョン(100000)は、非常に将来のバージョンを指していますが、まだ決まっていないためで、そう遠くない将来に deprecated になりそうです。
[SwiftUI] UIImagePickerController を SwiftUI から使う方法
PHPickerViewController
Apple のドキュメントは、こちら。
UIImagePickerController からの違いは以下です。
- アクセスすることに対して、ユーザーの許諾を得る必要はない(WWDC2020 のビデオでは、許諾を得ようとするな と言ってます)
- PHPickerViewController 内で カメラを使って撮影して、その写真を使用することはできない
- UIImage 等のデータを取得するために、ItemProvider が返される
- PHAsset にアクセスしたければ、渡された ID から fetch しないといけない (ユーザーからの許諾が必要となるはずです)
PHPicker を使う
PHPickerViewController は、初期化時に、 PHPickerConfiguration を使用して、その動作を決める必要があります。
写真等を選択された後の動作を決めるために、delegate(PHPickerViewControllerDelegate) を指定できるようになっています。
Delegate のメソッドに渡される要素は、PHPickerResult の配列です。
UIImagePickerController では、UIImagePickerController.infoKey をキー値とする Dictionary でした
PHPickerConfiguration
PHPickerConfiguration を使用して、選択対象等を設定します。
Apple のドキュメントは、こちら。
- filter
- .image や .video を指定することで、選択対象をフィルターすることができます
- selectionLimit
- 要素の選択数を指定できます。デフォルトは1です。0を指定すると、複数(システムが許す最大数)指定となります。
- preferredAssetRepresentationMode
- automatic, compatible, current から選べますが、その意味の説明は、Apple のドキュメントには記載されていません。デフォルトは、automatic です。
PHPickerViewControllerDelegate
要素選択終了時/キャンセル時 に PHPickerViewController に設定された delegate のメソッドが呼ばれます。
Apple のドキュメントは、こちら。
PHPickerViewControllerDelegate が要求するメソッドは、以下の1つです。
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult])
キャンセルされた時は、results が 空配列になって呼び出されます。
PHPickerResult
選択された要素の情報が保持されています。
Apple のドキュメントは、こちら。
以下のプロパティを持っています。
- assetIdentifier
- 必要であれば、この ID を使って、PHAsset を取得します
- itemProvider
- 通常は、このプロパティ経由でデータを取得します
D&D でも使われている ItemProvider です。
if itemProvider.canLoadObject(ofClass: UIImage.self) {
itemProvider.loadObject(ofClass: UIImage.self) { image, error in
if let image = image as? UIImage {
// process image here
}
if let error = error {
// error ?
}
}
}
PHPicker を SwiftUI で使えるようにする
UIViewControllerRepresentable で wrap する
PHPickerViewController は、ViewController なので、SwiftUI で使うためには、UIViewController Representable で wrap します
使用する時に、初期化用の PHPickerConfiguration と PHPickerViewControllerDelegate で呼び出される closure を指定するようにしました。
//
// SwiftUIPHPicker.swift
//
// Created by : Tomoaki Yagishita on 2020/12/07
// © 2020 SmallDeskSoftware
//
import Foundation
import SwiftUI
import PhotosUI
import os
public typealias PHPickerViewCompletionHandler = ( ([PHPickerResult]) -> Void)
public struct SwiftUIPHPicker: UIViewControllerRepresentable {
// 1
var configuration: PHPickerConfiguration
// 2
var completionHandler: PHPickerViewCompletionHandler?
let logger = Logger(subsystem: "com.smalldesksoftware.SwiftUIPHPicker", category: "SwiftUIPHPicker")
public init(configuration: PHPickerConfiguration, completion: PHPickerViewCompletionHandler? = nil) {
self.configuration = configuration
self.completionHandler = completion
}
public func makeCoordinator() -> Coordinator {
logger.debug("makeCoordinator called")
return Coordinator(self)
}
//(3)
public func makeUIViewController(context: Context) -> PHPickerViewController {
logger.debug("makeUIViewController called")
let viewController = PHPickerViewController(configuration: configuration)
viewController.delegate = context.coordinator
return viewController
}
//(4)
public func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) {
logger.debug("updateUIViewController called")
}
public class Coordinator : PHPickerViewControllerDelegate {
let parent: SwiftUIPHPicker
init(_ parent: SwiftUIPHPicker) {
self.parent = parent
}
//(5)
public func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
parent.logger.debug("didFinishPicking called")
picker.dismiss(animated: true)
// (6)
self.parent.completionHandler?(results)
}
}
}
- PHPickerConfiguration は、外部から受け取ったものを使います
- completionHandler は、([PHPickerResult]) -> Void の closure で、delegate が呼び出された時に呼ばれます。空であれば、PHPicker を閉じるだけです
- PHPickerViewController を生成した時に、Coordinator を delegate に設定します
- updateUIViewController は、現時点では空実装です
- delegate メソッドは、Coordinator に実装しています
- delegate が呼ばれた時に、初期化時に指定した closure を呼び出します
サンプルコード
以下が、SwiftUIPHPicker (PHPickerViewController を wrap したもの)を使うコードの例です。
//
// ContentView.swift
//
// Created by : Tomoaki Yagishita on 2020/12/07
// © 2020 SmallDeskSoftware
//
import SwiftUI
import PhotosUI
import os
import SwiftUIPHPicker
struct ContentView: View {
// (1)
@State private var images:[UIImage] = []
// (2)
@State private var showPHPicker:Bool = false
// (3)
static var config: PHPickerConfiguration {
var config = PHPickerConfiguration()
config.filter = .images
return config
}
let logger = Logger(subsystem: "com.smalldesksoftware.PHPickerSample", category: "PHPickerSample")
var columns: [GridItem] = Array(repeating: .init(.fixed(100)), count: 3)
var body: some View {
VStack {
HStack {
Spacer()
Button(action: {
showPHPicker.toggle()
}, label: {
Image(systemName: "plus")
.font(.largeTitle)
})
}
.padding()
Spacer()
LazyVGrid(columns: columns) {
ForEach(images, id: \.self) { image in
Image(uiImage: image)
.resizable().scaledToFit()
}
}
.padding()
Spacer()
}
.sheet(isPresented: $showPHPicker) {
// (4)
SwiftUIPHPicker(configuration: ContentView.config) { results in
for result in results {
let itemProvider = result.itemProvider
if itemProvider.canLoadObject(ofClass: UIImage.self) {
itemProvider.loadObject(ofClass: UIImage.self) { image, error in
if let image = image as? UIImage {
DispatchQueue.main.async {
// (5)
self.images.append(image)
}
}
if let error = error {
logger.error("\(error.localizedDescription)")
}
}
}
}
}
}
}
}
- @State 指定した images に画像を保持します。保持された画像は、LazyVGrid を使って表示されます
- SwiftUIPHPicker を .sheet 設定しています。そこで使われる表示非表示フラグです。
- PHPickerViewController で使用される configuration です。イメージを1つ 選択する設定です。”config.selectionLimit = 0″ と追加すると複数枚選択になります。
- SwiftUIPHPicker は Sheet として表示されるように設定します
- itemProvider から UIImage が取れた時は、 images に追加していきます。(@State 変数なので、UI の更新は自動に行われます)
GitHub
上記のコードを GitHub に入れてあります。SwiftPM に対応していますので、Xcode から URL を入れるだけで使えます。
まとめ:PHPickerViewController の使い方
- ユーザーにライブラリへのアクセス許諾をしなくても使える
- UIImagePickerController を置き換える
- configuration.filter で選択対象要素の種類を指定できる
- configuration.selectionLimit で選択要素数を指定できる
説明は以上です。
不明な点やおかしな点ありましたら、こちらまで。
Sponsor Link