Improve access to Photos in your app
Photos & Camera 进阶 24m

改善你的 app 对照片库的访问

Improve access to Photos in your app

2021年6月10日

在 Apple 官方观看视频

一句话判断

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.creationDatePHAsset.modificationDate 现在有更精确的子秒级时间戳。
WWDC 2021