适配 苹果强推此次权限改动同时希望开发者们能够反思他们的 APP 是否真的需要那么多的相册权限,遵循使用最少的权限来实现功能,保护用户的隐私不被侵害应该是每个开发者应该承担的责任。
重新审查相册权限
iOS 14 的相册资源访问方式细分为了 writeOnly 以及 readWrite 方式,作为开发者需要重新审查项目需要的权限。
只读权限 细心的同学可能会发现苹果并未提供 readOnly 权限,对于只需要读取图片资源类的 APP,苹果建议是使用 iOS 14 提供的图片选择器 PHPicker 来选择图片资源(iOS 14 以下对应 UIImagePickerController)。PHPicker 有很多优点:
PHPicker 是独立进程,不影响 APP 的性能表现
PHPicker 不需要用户授予权限就可以选择用户所有的资源
PHPicker 支持多选
以下是一个简单的 Demo:
调起选择图片控件后显示如下:
PHPicker UI 是固定的,如果需要定制 UI,建议还是自行调用 PhotoKit 实现图片选择器。
只写权限 只写权限是指 APP 只允许写入相册。只写权限的弹窗比读取权限简洁,只有允许和不允许的选项,使用一个 Demo 来验证一下:
首先需要在 Info.plist 下配置权限信息:
NSPhotoLibraryAddUsageDeion : 用户存入相册时的提示信息。
NSPhotoLibraryUsageDeion : 相册访问权限信息,必须有此项,不然访问相册的时候 APP 会 Crash。
分别使用 AssetsLibrary 、 PhotoKit 以及 UIKit 层 UIImageWriteToSavedPhotosAlbum(:::) 写入相册:
很容易发现使用 AssetsLibrary 弹出来的是 Access 的权限,并不是苹果声称的独立弹窗,并且点击 选择照片 后没有弹出系统弹窗选择图片,但是能够正常写入相册。PhotoKit 与 UIKit 的实现符合预期。
读写权限
读写权限相对于只写权限只是增加了读权限,权限配置与只写权限一致,这里就不赘述了。同样分别使用 AssetsLibrary 和 PhotoKit 来读取相册资源,两者权限弹窗表现一致:
选择 Limited Photo 模式, PhotoKit 访问正常, AssetsLibrary 访问相册出错,猜测苹果并未适配 AssetsLibrary :
报错信息如下:
选择 Full Photo 模式,两者表现一致。
AssetsLibrary 适配
从上一节来看, AssetsLibrary 在读写权限上面都存在问题,苹果似乎已经放弃维护 AssetsLibrary 了,所以建议还在使用 AssetsLibrary 管理资源的尽快迁移到 PhotoKit 。
一些不可忽略的细节
在用户选择了 Limited Photo 模式时,绝大多数的 API 的表现都与 Full Photo 模式一致,但是有少数的几个 API 需要注意一下:
使用 PHAssetCreationRequests 来创建的资源默认是会添加在用户允许的集合当中
不能获取以及创建相册
不能访问用户 iCloud 共享的相册
隐藏系统自动弹出的选择图片弹窗 当用户选择 Limited Photo 模式时,APP 在每次冷启第一次访问 PhotoKit 的时候,系统会自动弹出一个选择资源的弹窗:
这种交互模式对于用户来说相当不友好,官方建议关闭自动弹窗。比较理想的交互方式在用户选择了 Limited Photo 模式时,在特定界面(例如设置页)显示修改资源的入口,当用户主动点击该入口时弹出系统弹窗。
首先我们需要关闭系统自动弹窗:在 Info.plist 文件中添加新键 PHPhotoLibraryPreventAutomaticLimitedAccessAlert ,设置其值为 YES:
在用户点击修改资源入口时调用 PhotoKit API 弹出选择资源界面:
if dataSource?.count == 0 {
if #available(iOS 14, *) {
PHPhotoLibrary.shared().presentLimitedLibraryPicker(from: weakSelf!)
} else
{
// Fallback on earlier versions
// let imagePickerController = UIImagePickerController()
// imagePickerController.sourceType = .photoLibrary
//
// weakSelf!.present(imagePickerController, animated: true, completion: nil)
}