WWDC20-10652-认识新照片选择器

- 7 mins

让人们选择在您的应用程序中使用的照片和视频,而无需完全访问照片库。了解适用于iOS和Mac Catalyst的PHPicker API如何确保隐私,同时为您的应用程序提供您所需的功能。

PHPicker是UIImagePickerController的现代替代品。除了以隐私为重点的方法外,API还为您的应用程序提供了其他功能,如搜索、多图像选择以及在照片网格上放大或缩小的能力。我们将向您展示PHPicker如何帮助大多数应用程序避免要求直接访问库,以及如何实现它,以改善与您的应用程序交互的人的整体体验。

介绍PHPicker

PHPicker:一个新的系统提供的选择器,允许您从用户的照片库中访问照片和视频。

建议您使用此选择器,而不是构建自己的自定义照片选择用户界面。

新版本包括:

PHPicker默认内置隐私保护:

PHPicker不是单个类的名称,而是一组一起工作的类。

API说明

PHPickerConfiguration– 允许您指定限制和过滤器:

PHPickerViewController– 处理选择器的主视图控制器:

PHPickerViewControllerDelegate– 挑选者的代表:

PHPickerResult– 这些对象的数组作为响应传递给应用程序

您通常可以从PHPickerResult中提取照片,而无需访问PHPhotoLibrary,但如果您确实需要访问照片库,请将其传递给PHPickerConfiguration.init,并从选择器结果中获取assetIdentifier引用。

如果您使用具有照片库访问权限的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
    }
}