Sponsor Link
目次
UIImagePickerController
WWDC2020 の“Meet the new Photos picker”をみると、UIImagePickerController を PHPicker に切り替えることを勧めているようです。
ドキュメントを確認しても、まだ、Decprecated にはなっていませんが、強い理由がなければ、今後の開発には PHPicker を使用する方が良いようです。
1 2 3 |
@available(iOS, introduced: 11.0, deprecated: 100000, message: "Will be removed in a future release, use PHPicker.") |
deprecated になるバージョン(100000)は、非常に将来のバージョンを指していますが、まだ決まっていないためで、そう遠くない将来に deprecated になりそうです。

PHPickerViewController
Apple のドキュメントは、こちら。
UIImagePickerController からの違いは以下です。
- アクセスすることに対して、ユーザーの許諾を得る必要はない(WWDC2020 のビデオでは、許諾を得ようとするな と言ってます)
- PHPickerViewController 内で カメラを使って撮影して、その写真を使用することはできない
- UIImage 等のデータを取得するために、ItemProvider が返される
- PHAsset にアクセスしたければ、渡された ID から fetch しないといけない (ユーザーからの許諾が必要となるはずです)
PHPicker を使う
PHPickerViewController は、初期化時に、 PHPickerConfiguration を使用して、その動作を決める必要があります。
写真等を選択された後の動作を決めるために、delegate(PHPickerViewControllerDelegate) を指定できるようになっています。
Delegate のメソッドに渡される要素は、PHPickerResult の配列です。
PHPickerConfiguration
PHPickerConfiguration を使用して、選択対象等を設定します。
Apple のドキュメントは、こちら。
- filter
- .image や .video を指定することで、選択対象をフィルターすることができます
- selectionLimit
- 要素の選択数を指定できます。デフォルトは1です。0を指定すると、複数(システムが許す最大数)指定となります。
- preferredAssetRepresentationMode
- automatic, compatible, current から選べますが、その意味の説明は、Apple のドキュメントには記載されていません。デフォルトは、automatic です。
PHPickerViewControllerDelegate
要素選択終了時/キャンセル時 に PHPickerViewController に設定された delegate のメソッドが呼ばれます。
Apple のドキュメントは、こちら。
PHPickerViewControllerDelegate が要求するメソッドは、以下の1つです。
1 2 3 |
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) |
キャンセルされた時は、results が 空配列になって呼び出されます。
PHPickerResult
選択された要素の情報が保持されています。
Apple のドキュメントは、こちら。
以下のプロパティを持っています。
- assetIdentifier
- 必要であれば、この ID を使って、PHAsset を取得します
- itemProvider
- 通常は、このプロパティ経由でデータを取得します
1 2 3 4 5 6 7 8 9 10 11 12 |
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 を指定するようにしました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
// // 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 したもの)を使うコードの例です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
// // 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