改善你的 app 对照片库的访问
Improve access to Photos in your app
2021年6月10日
一句话判断
iOS 15 对照片权限模型做了细粒度拆分——用户可以选择只授权「选中的照片」而不是整个相册,你的 app 必须适配这个变化,否则会在权限检查处直接崩溃。
这场 Session 讲了什么
Photos 框架在 iOS 15 经历了一次重要的权限模型变更。之前的「全部照片 / 无权限」二元选择被细化为三级:全部照片、选中的照片、无权限。「选中的照片」是一个全新的权限级别——用户可以选择只把相册中的几张照片授权给你的 app。
这意味着 PHPhotoLibrary.authorizationStatus() 现在可能返回 .limited。如果你的代码只检查了 .authorized 和 .denied,在 .limited 状态下会出现问题。
Session 详细讲解了如何处理 .limited 权限。使用 PHAsset.fetchAssets 时,系统只会返回用户选中的那些照片。如果你的 app 需要访问更多照片,需要引导用户到系统设置中修改权限。
另一个重要更新是 PHPickerConfiguration 的改进。iOS 15 允许你在 picker 中设置 selection 属性来预设已选中的照片,还可以配置 selectionPolicy 来控制多选行为。
值得深挖的点
Limited 权限下的数据一致性问题
当用户授予 .limited 权限时,PHAsset.fetchAssets 返回的结果只包含选中的照片。但问题是:用户可能随时在系统设置中增减授权的照片。这意味着你之前 fetch 的结果可能已经过期。Session 建议使用 PHPhotoLibrary.register() 监听权限变化通知,在用户修改授权范围时重新 fetch。
PHPicker 与直接访问的选择
如果你的 app 只需要让用户选择几张照片(比如头像上传),用 PHPickerViewController 就够了——它不需要任何照片权限。只有当你需要浏览整个相册、读取照片元数据或者做批量操作时,才需要请求 PHPhotoLibrary 权限。
代码片段
// 正确处理所有权限级别
import Photos
class PhotoAccessManager {
let library = PHPhotoLibrary.shared()
func requestAccess() async -> Bool {
// 使用新的 async API 请求权限
let status = await PHPhotoLibrary.requestAuthorization(for: .readWrite)
return handleStatus(status)
}
func handleStatus(_ status: PHAuthorizationStatus) -> Bool {
switch status {
case .authorized:
// 用户授权了全部照片
return true
case .limited:
// 用户只授权了选中的照片
// fetchAssets 只返回选中的照片
return true
case .denied, .restricted:
// 没有权限,需要引导用户去设置
showPermissionDeniedAlert()
return false
case .notDetermined:
return false
@unknown default:
return false
}
}
func showPermissionDeniedAlert() {
guard let url = URL(string: UIApplication.openSettingsURLString) else { return }
if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url)
}
}
}
// 使用 PHPicker 选择照片(不需要权限)
import PhotosUI
class PhotoPicker: NSObject, PHPickerViewControllerDelegate {
func presentPicker(from viewController: UIViewController) {
var config = PHPickerConfiguration()
config.selectionLimit = 5 // 最多选 5 张
config.filter = .images // 只显示图片
let picker = PHPickerViewController(configuration: config)
picker.delegate = self
viewController.present(picker, animated: true)
}
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
picker.dismiss(animated: true)
for result in results {
let provider = result.itemProvider
if provider.hasItemConformingToTypeIdentifier(UTType.image.identifier) {
provider.loadObject(ofClass: UIImage.self) { image, error in
guard let image = image as? UIImage else { return }
DispatchQueue.main.async {
// 处理选中的图片
}
}
}
}
}
}
// 监听权限变化
func registerForChanges() {
PHPhotoLibrary.shared().register(self)
}
// PHPhotoLibraryChangeObserver
func photoLibraryDidChange(_ changeInstance: PHChange) {
// 用户可能修改了授权范围,需要重新获取照片
DispatchQueue.main.async {
self.reloadPhotos()
}
}
最佳实践
如果你的 app 之前只检查 .authorized,现在必须加上 .limited 的处理。在 .limited 状态下,PHAsset.fetchAssets 不会返回错误,只是结果集变小了。最容易出问题的地方是:你的 UI 假设「相册为空」意味着用户没有照片,但实际上可能是用户只授权了部分照片。
推荐使用 PHPickerViewController 作为默认的图片选择方案。只有当你需要创建自定义的照片浏览器、需要访问照片元数据(位置、日期、EXIF)或者需要编辑照片时,才请求完整的照片权限。
还有什么值得关注
- iOS 15 允许用户在「设置 > 隐私 > 照片」中为每个 app 单独管理授权的照片列表。
PHPickerConfiguration新增了selection属性,可以预选照片。PHAsset.creationDate和PHAsset.modificationDate现在有更精确的子秒级时间戳。