WWDC20-10652-认识新照片选择器
- 7 mins让人们选择在您的应用程序中使用的照片和视频,而无需完全访问照片库。了解适用于iOS和Mac Catalyst的PHPicker API如何确保隐私,同时为您的应用程序提供您所需的功能。
PHPicker是UIImagePickerController的现代替代品。除了以隐私为重点的方法外,API还为您的应用程序提供了其他功能,如搜索、多图像选择以及在照片网格上放大或缩小的能力。我们将向您展示PHPicker如何帮助大多数应用程序避免要求直接访问库,以及如何实现它,以改善与您的应用程序交互的人的整体体验。
介绍PHPicker
PHPicker:一个新的系统提供的选择器,允许您从用户的照片库中访问照片和视频。
建议您使用此选择器,而不是构建自己的自定义照片选择用户界面。
新版本包括:
- 访问“照片”中照片和视频数据的最好方式
- 全新设计
- 新的易用API
- 支持多项选择
- 缩放手势
- 指定可选择类型
PHPicker默认内置隐私保护:
- 独立进程,通过XPC与应用程序对话,屏幕不能截屏
- 只有用户选择的内容才会被传回宿主App
- 您的应用程序不会直接访问照片库
- 它不需要获得照片库权限(除非你真的需要,否则不要要求它)
PHPicker不是单个类的名称,而是一组一起工作的类。
API说明
PHPickerConfiguration
– 允许您指定限制和过滤器:
selectionLimit
–可以选择的项目数量(1默认,0 = 无限)filter
– e.g..images
or.any(of: [.videos, .livePhotos])
PHPickerViewController
– 处理选择器的主视图控制器:
- the picker doesn’t dismiss itself automatically, call
picker.dismiss(animated:)
when you get the response
PHPickerViewControllerDelegate
– 挑选者的代表:
PHPickerResult
– 这些对象的数组作为响应传递给应用程序
- 从结果中获取
itemProvider
- 检查
itemProvider.canLoadObject(ofClass: UIImage.self)
- 通过
itemProvider.loadObject(ofClass: UIImage.self) { … }
您通常可以从PHPickerResult
中提取照片,而无需访问PHPhotoLibrary
,但如果您确实需要访问照片库,请将其传递给PHPickerConfiguration.init
,并从选择器结果中获取assetIdentifier
引用。
如果您使用具有照片库访问权限的PHPicker,并且您只能有限地访问照片子集,那么:
- PHPicker仍然允许用户从整个库中选择照片
- 但您可以直接访问的选择不会因他们在选择器中选择的内容而扩展
不建议使用UIImagePickerController
的照片库API。
Demo:
使用PHPickerConfiguration
import PhotosUI
var configuration = PHPickerConfiguration()
// “unlimited” selection by specifying 0, default is 1
configuration.selectionLimit = 0
// Only show images (including Live Photos)
configuration.filter = .images
// Uncomment next line for other example: Only show videos or Live Photos (for their video complement), but no images
// configuration.filter = .any(of: [.videos, .livePhotos])
使用PHPickerViewController
import UIKit
import PhotosUI
class SingleSelectionPickerViewController: UIViewController, PHPickerViewControllerDelegate {
@IBAction func presentPicker(_ sender: Any) {
var configuration = PHPickerConfiguration()
// Only wants images
configuration.filter = .images
let picker = PHPickerViewController(configuration: configuration)
picker.delegate = self
// The client is responsible for presentation and dismissal
present(picker, animated: true)
}
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
// The client is responsible for presentation and dismissal
picker.dismiss(animated: true)
// Get the first item provider from the results, the configuration only allowed one image to be selected
let itemProvider = results.first?.itemProvider
if let itemProvider = itemProvider, itemProvider.canLoadObject(ofClass: UIImage.self) {
itemProvider.loadObject(ofClass: UIImage.self) { (image, error) in
// TODO: Do something with the image or handle the error
}
} else {
// TODO: Handle empty results or item provider not being able load UIImage
}
}
}
使用单选
import UIKit
import PhotosUI
class ViewController: UIViewController {
@IBOutlet weak var imageView: UIImageView!
@IBAction func presentPicker(_ sender: Any) {
var configuration = PHPickerConfiguration()
configuration.filter = .images
let picker = PHPickerViewController(configuration: configuration)
picker.delegate = self
present(picker, animated: true)
}
}
extension ViewController: PHPickerViewControllerDelegate {
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
dismiss(animated: true)
if let itemProvider = results.first?.itemProvider, itemProvider.canLoadObject(ofClass: UIImage.self) {
let previousImage = imageView.image
itemProvider.loadObject(ofClass: UIImage.self) { [weak self] image, error in
DispatchQueue.main.async {
guard let self = self, let image = image as? UIImage, self.imageView.image == previousImage else { return }
self.imageView.image = image
}
}
}
}
}
使用多选
import UIKit
import PhotosUI
class ViewController: UIViewController {
@IBOutlet weak var imageView: UIImageView!
var itemProviders: [NSItemProvider] = []
var iterator: IndexingIterator<[NSItemProvider]>?
@IBAction func presentPicker(_ sender: Any) {
var configuration = PHPickerConfiguration()
configuration.filter = .images
configuration.selectionLimit = 0
let picker = PHPickerViewController(configuration: configuration)
picker.delegate = self
present(picker, animated: true)
}
func displayNextImage() {
if let itemProvider = iterator?.next(), itemProvider.canLoadObject(ofClass: UIImage.self) {
let previousImage = imageView.image
itemProvider.loadObject(ofClass: UIImage.self) { [weak self] image, error in
DispatchQueue.main.async {
guard let self = self, let image = image as? UIImage, self.imageView.image == previousImage else { return }
self.imageView.image = image
}
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
displayNextImage()
}
}
extension ViewController: PHPickerViewControllerDelegate {
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
dismiss(animated: true)
itemProviders = results.map(\.itemProvider)
iterator = itemProviders.makeIterator()
displayNextImage()
}
}
PHPicker 和 PhotoKit 配合使用
import UIKit
import PhotosUI
class PhotoKitPickerViewController: UIViewController, PHPickerViewControllerDelegate {
@IBAction func presentPicker(_ sender: Any) {
let photoLibrary = PHPhotoLibrary.shared()
let configuration = PHPickerConfiguration(photoLibrary: photoLibrary)
let picker = PHPickerViewController(configuration: configuration)
picker.delegate = self
present(picker, animated: true)
}
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
picker.dismiss(animated: true)
let identifiers = results.compactMap(\.assetIdentifier)
let fetchResult = PHAsset.fetchAssets(withLocalIdentifiers: identifiers, options: nil)
// TODO: Do something with the fetch result if you have Photos Library access
}
}