Highlight
iOS 14 引入了全新的 Photos picker(
PHPickerViewController),替代了之前的UIImagePickerController和手动请求相册权限然后用PHAsset筛选的方式。
核心内容
很多 App 只想完成一个很小的任务:换头像、给聊天消息附一张图、把几张照片导入编辑器。过去常见做法是请求完整照片库权限,然后用 PhotoKit 或旧的 UIImagePickerController 把照片拿进来。这个权限范围太大。用户只是点了一次“选照片”,App 却可能获得整座照片库的访问入口。
iOS 14 的 PHPickerViewController 把这个流程改成系统代办。选择器由系统提供,运行在 App 之外的独立进程中。App 看不到选择器里的其他内容,也不能截取选择器界面;只有用户确认选择的照片和视频会作为结果传回 App。(01:06)
这个变化还顺手解决了旧选择器的体验问题。新的 Photos picker 有和系统照片 App 一致的网格、搜索、缩放、多选和选中内容预览。开发者用配置对象限制可选类型和选择数量,再通过 delegate 接收结果。(00:24)
Apple 给出的边界很清楚。大多数只需要照片或视频数据的 App,不需要直接使用 PhotoKit。只有非破坏性图片编辑、照片库整理这类必须操作 PHAsset 的 App,才应该请求照片库权限,并且要处理 iOS 14 的 Limited Photos Library。(10:15)
详细内容
配置选择范围和多选数量
(02:27)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])
关键点:
import PhotosUI引入 Photos picker 所在框架。PHPickerConfiguration()创建一个可变配置对象,后续选择数量和过滤条件都写在这里。selectionLimit默认是 1;设为 0 表示无限制多选。.images会显示图片,也包含 Live Photo。.any(of: [.videos, .livePhotos])是官方片段里的另一个过滤示例,用来显示视频和 Live Photo 的视频部分。
呈现 picker 并接收结果
(03:07)配置完成后,用它初始化 PHPickerViewController。App 自己负责呈现和关闭 picker;用户确认后,delegate 会拿到 [PHPickerResult]。
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
}
}
}
关键点:
- 控制器需要遵守
PHPickerViewControllerDelegate,否则收不到用户选择结果。 PHPickerViewController(configuration:)把前面的配置变成系统选择器界面。picker.delegate = self指定当前控制器接收选择完成回调。didFinishPicking的results可能为空;用户取消选择时就会出现这种情况。PHPickerResult通过itemProvider提供数据表示,加载前先用canLoadObject(ofClass:)检查类型。loadObject(ofClass:)是异步 API,回调里要处理错误,并把 UI 更新切回主线程。
多选时按需加载图片
(07:34)多选并不要求一次性把所有图片加载进内存。演示 App 只保存返回的 NSItemProvider,再用迭代器按需加载下一张图片。
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()
}
}
关键点:
selectionLimit = 0打开无限制多选,picker 会显示适合多选的界面和 Add 按钮。itemProviders只保存数据提供者引用,不提前解码所有图片。iterator记录当前显示进度,displayNextImage()每次只处理下一项。previousImage和guard用来避免异步回调覆盖已经变化的imageView。touchesEnded在演示里用于切到下一张图,真实 App 可以把同样逻辑接到按钮、分页或缩略图列表。results.map(\.itemProvider)把所有选择结果转换成后续可加载的数据源。
需要 PHAsset 时接回 PhotoKit
(11:13)少数 App 需要继续使用 PhotoKit,例如非破坏性编辑或照片库整理。此时可以用 PHPhotoLibrary 初始化配置,并在结果里读取 assetIdentifier,再用标准 PhotoKit API fetch PHAsset。
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
}
}
关键点:
PHPhotoLibrary.shared()取到当前照片库对象,配置对象用它和PhotoKit对齐。PHPickerConfiguration(photoLibrary:)是需要PHAsset标识符时使用的初始化方式。results.compactMap(\.assetIdentifier)过滤掉没有本地资产标识符的结果。PHAsset.fetchAssets(withLocalIdentifiers:options:)把标识符转换成PHAsset查询结果。- 注释强调了权限前提:只有已经拥有 Photos Library access 的 App 才能对 fetch 结果做后续处理。
- Limited Photos Library 不会因为用户在 picker 中选择了更多照片就自动扩大
PHAsset可访问范围;需要额外授权时,要使用专门的 limited library 管理流程。
核心启发
-
做什么:把头像上传改成一次性 Photos picker
为什么值得做:头像上传只需要一张图片,不该先请求完整照片库权限。
PHPickerViewController可以让用户直接选择图片,App 只拿到这一次选择的结果。怎么开始:创建
PHPickerConfiguration(),设置configuration.filter = .images,保持默认selectionLimit = 1,在didFinishPicking里通过itemProvider.loadObject(ofClass: UIImage.self)读取图片。 -
做什么:给聊天或笔记 App 增加多图插入
为什么值得做:session 展示了 picker 的多选、选区预览和 Add 按钮,用户可以一次挑好一组图片。
怎么开始:把
configuration.selectionLimit设为 0 或产品允许的数量;回调里保存results.map(\.itemProvider),按消息发送顺序或插入顺序逐个加载。 -
做什么:为图片编辑器建立低权限导入入口
为什么值得做:很多编辑器只需要用户当前选中的图片数据,不需要浏览整座照片库。picker 返回的
NSItemProvider已经能加载UIImage。怎么开始:先用
PHPickerViewController做默认导入;如果某个高级功能确实要追踪PHAsset,再在该功能入口请求照片库访问。 -
做什么:给照片库整理类 App 做 PhotoKit 分支
为什么值得做:整理相册、非破坏性编辑这类 App 需要
PHAsset和照片库访问状态。session 给出从 picker 结果取assetIdentifier再 fetchPHAsset的路径。怎么开始:用
PHPickerConfiguration(photoLibrary:)初始化选择器;在回调里收集assetIdentifier;检测用户是否授予 Photos Library access;需要扩大PHAsset访问范围时,再接入 session 提到的额外授权流程。
关联 Session
- Handle the Limited Photos Library in your app — 讲解 iOS 14 的 Limited Photos Library,以及 PhotoKit App 何时应该改用 PHPicker。
- Build trust through better privacy — 从权限、透明度和用户控制角度解释 iOS 14 的隐私设计。
- Build with iOS pickers, menus and actions — 介绍 UIKit 中新的 picker、menu 和 action 控件,适合和系统照片选择入口一起理解。
- Design with iOS pickers, menus and actions — 从设计角度说明 picker、menu 和 action 应该放在什么交互场景里。
评论
GitHub Issues · utterances