PHPicker
iOS 14 中系统新增了一个图片选择器 PHPicker,官方建议使用 PHPicker 来替代原有的 API 进行图片选择,下面我们来看看 PHPicker 的优点:
-
支持多选
-
支持搜索
-
独立的进程
-
内置隐私
-
不需要直接访问用户相册
-
不会弹出访问相册提示
-
仅提供用户选择的照片和视频(App 无法获取其他照片)
如何调用 PHPicker
<figcaption style="box-sizing: border-box; list-style: inherit; margin: 5px auto; font-size: 14px; text-align: center; color: rgb(102, 102, 102); max-width: 61.8%; border: 3px solid transparent; border-radius: 2px; transition: border-color 0.1s ease 0s; cursor: pointer; font-style: italic;">添加描述</figcaption>
我们先来看下 PHPicker 的流程图,首先声明 PHPickerConfiguration,进行配置,再传给 PHPickerViewController,完成调用环节,代码如下:
<pre class="prism-token token language-javascript" style="box-sizing: border-box; list-style: inherit; margin: 0.5em 0px; padding: 1em; color: rgb(204, 204, 204); background: rgb(80, 85, 107); border-radius: 3px; overflow: auto; font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace; overflow-wrap: normal; text-align: left; white-space: pre; word-spacing: normal; word-break: normal; line-height: 1.5; tab-size: 4; hyphens: none;">
var config = PHPickerConfiguration() // 可选择的资源数量,0表示不设限制,默认为1 config.selectionLimit = 0 // 可选择的资源类型 // 只显示图片(注:images 包含 livePhotos) config.filter = .images // 显示 Live Photos 和视频(注:livePhotos 不包含 images) config.filter = .any(of: [.livePhotos, .videos]) // 如果要获取视频,最好设置该属性,避免系统对视频进行转码 config.preferredAssetRepresentationMode = .current let picker = PHPickerViewController(configuration: config) picker.delegate = self present(picker, animated: true, completion: nil)复制代码
</pre>
###
处理 PHPicker 的回调
PHPicker 的代理方法只有一个,声明如下:
<pre class="prism-token token language-javascript" style="box-sizing: border-box; list-style: inherit; margin: 0.5em 0px; padding: 1em; color: rgb(204, 204, 204); background: rgb(80, 85, 107); border-radius: 3px; overflow: auto; font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace; overflow-wrap: normal; text-align: left; white-space: pre; word-spacing: normal; word-break: normal; line-height: 1.5; tab-size: 4; hyphens: none;">
@available(iOS 14, *) public protocol PHPickerViewControllerDelegate : AnyObject { func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) }复制代码
注意: 取消选择也会触发代理方法,会返回空的 results。
如何获取照片
PHPicker 获取图片的方法还是比较简单的,代码如下:
<pre class="prism-token token language-javascript" style="box-sizing: border-box; list-style: inherit; margin: 0.5em 0px; padding: 1em; color: rgb(204, 204, 204); background: rgb(80, 85, 107); border-radius: 3px; overflow: auto; font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace; overflow-wrap: normal; text-align: left; white-space: pre; word-spacing: normal; word-break: normal; line-height: 1.5; tab-size: 4; hyphens: none;">
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { // 首先需要 dismiss picker picker.dismiss(animated: true, completion: nil) for result in results { // 判断类型是否为 UIImage if result.itemProvider.canLoadObject(ofClass: UIImage.self) { // 确认类型后,调用 loadObject 方法获取图片 result.itemProvider.loadObject(ofClass: UIImage.self) { (data, error) in // 回调结果是在异步线程,展示时需要切换到主线程 if let image = data as? UIImage { DispatchQueue.main.async { self.showImage(image) } } } } } }复制代码
如何获取视频
其他文章中都没有介绍 PHPicker 如何获取视频,其实获取视频的方法在官方的 Demo 以及视频中都没有介绍,这也是我迟迟没有写文章的原因,因为之前我也不知道怎么获取,那么下面让我们一起来看下怎么获取视频。
<pre class="prism-token token language-javascript" style="box-sizing: border-box; list-style: inherit; margin: 0.5em 0px; padding: 1em; color: rgb(204, 204, 204); background: rgb(80, 85, 107); border-radius: 3px; overflow: auto; font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace; overflow-wrap: normal; text-align: left; white-space: pre; word-spacing: normal; word-break: normal; line-height: 1.5; tab-size: 4; hyphens: none;">
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { // 首先需要 dismiss picker picker.dismiss(animated: true, completion: nil) for result in results { if result.itemProvider.canLoadObject(ofClass: UIImage.self) { // 判断类型是否为 UIImage ... } else { // 类型为 Video // 调用 loadFileRepresentation 方法获取视频的 url // 这里 Type Identifier 我们用 UTType.movie.identifier (“public.movie”) 这个 UTI 可以获取所有格式的视频 result.itemProvider.loadFileRepresentation(forTypeIdentifier: UTType.movie.identifier) { (url, error) in if let error = error { print(error) return } // 系统会将视频文件存放到 tmp 文件夹下 // 我们必须在这个回调结束前,将视频拷贝出去,一旦回调结束,系统就会把视频删掉 // 所以一定要确定拷贝结束后,再切换到主线程做 UI 操作 // 另外不用担心视频过大而导致拷贝的时间很久,系统将创建一个 APFS 的克隆项,因此拷贝的速度会非常快 guard let url = url else { return } let fileName = "\(Int(Date().timeIntervalSince1970)).\(url.pathExtension)" let newUrl = URL(fileURLWithPath: NSTemporaryDirectory() + fileName) try? FileManager.default.copyItem(at: url, to: newUrl) DispatchQueue.main.async { self.playVideo(newUrl) } } } } }复制代码
注意: 如果你遇到了部分资源可以加载,而部分资源无法加载的话,那么有可能是设备没有连接到 iCloud,只能加载本地资源,而无法加载 iCould 上的资源。
被废弃的 API
有新的 API 出现,也会有一些 API 被废弃,在 UIImagePickerController 中有三个 sourceType,现在有两个被废弃,只留下 camera。
<pre class="prism-token token language-javascript" style="box-sizing: border-box; list-style: inherit; margin: 0.5em 0px; padding: 1em; color: rgb(204, 204, 204); background: rgb(80, 85, 107); border-radius: 3px; overflow: auto; font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace; overflow-wrap: normal; text-align: left; white-space: pre; word-spacing: normal; word-break: normal; line-height: 1.5; tab-size: 4; hyphens: none;">
public enum SourceType : Int { @available(iOS, introduced: 2, deprecated: 100000, message: "Will be removed in a future release, use PHPicker.") case photoLibrary = 0 case camera = 1 @available(iOS, introduced: 2, deprecated: 100000, message: "Will be removed in a future release, use PHPicker.") case savedPhotosAlbum = 2 }复制代码
另外 AssetsLibrary 早在几年前被废弃,如果还在使用 AssetsLibrary 请尽快使用新的 API。
PHPicker 的缺点
为什么不推荐使用 PHPicker,虽然说 PHPicker 有一些优点,但同时也有一些缺点:
-
加载 iCloud 资源时没有进度回调
-
不支持图片编辑(比如选择头像要将图片裁剪成正方形)
有没有其他的解决方案?
有的,如果你不能接受 PHPicker 的缺点,同时又想保护用户的隐私,目前有 Picker、Editor、Capture 三个模块,支持图片/视频选择、编辑、拍摄功能,支持 SPM、CocoaPods 方式引入。
新增权限
iOS 14 中相册新增了一个 “Limited Photos Library” 模式,在授权时多了一个 “选择照片” 的选项。点击之后系统会弹出 PHPickerController 用户可以选择指定的照片让 App 读取。iOS开发交流技术群:563513413,不管你是大牛还是小白都欢迎入驻
当用户选择了 limited 模式后,系统将在 App 每次启动后首次触发相册时弹出提示,允许用户修改需要授权给 App 的照片。
当然这个弹窗是可以关闭的,如果你希望手动控制 PHPickerController 弹出的时机也是有办法的。
我们需要在 Info.plist 中添加 PHPhotoLibraryPreventAutomaticLimitedAccessAlert 字段,并设置为 YES,设置后系统将不再弹出访问提示。
然后我们可以在合适的时机调用以下这个 API 来推出 PHPickerController。
<pre class="prism-token token language-javascript" style="box-sizing: border-box; list-style: inherit; margin: 0.5em 0px; padding: 1em; color: rgb(204, 204, 204); background: rgb(80, 85, 107); border-radius: 3px; overflow: auto; font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace; overflow-wrap: normal; text-align: left; white-space: pre; word-spacing: normal; word-break: normal; line-height: 1.5; tab-size: 4; hyphens: none;">
let viewController = self
PHPhotoLibrary.shared().presentLimitedLibraryPicker(from: viewController)复制代码
我们可以看到,当用户选择 limited 模式后,底部出现了一段提示:“无法查看相册全部照片,点击选择更多照片”。当点击这个提示后,将会推出 PHPickerController,此时用户可以修改授权给 App 的照片。同时我们会监听相册的变化,当用户修改授权的照片后,会立即刷新相册,用户可以继续进行选择照片的流程。
监听相册变化
配合手动调用 PHPickerController,我们还需要监听用户添加/删除了哪些照片。
注意: 这组 API 并不是新出的,从 iOS 8 开始就支持了。
<pre class="prism-token token language-javascript" style="box-sizing: border-box; list-style: inherit; margin: 0.5em 0px; padding: 1em; color: rgb(204, 204, 204); background: rgb(80, 85, 107); border-radius: 3px; overflow: auto; font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace; overflow-wrap: normal; text-align: left; white-space: pre; word-spacing: normal; word-break: normal; line-height: 1.5; tab-size: 4; hyphens: none;">
let viewController = self // 开始监听 PHPhotoLibrary.shared().register(viewController) // 结束监听 PHPhotoLibrary.shared().unregisterChangeObserver(viewController)复制代码
处理监听回调:
<pre class="prism-token token language-javascript" style="box-sizing: border-box; list-style: inherit; margin: 0.5em 0px; padding: 1em; color: rgb(204, 204, 204); background: rgb(80, 85, 107); border-radius: 3px; overflow: auto; font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace; overflow-wrap: normal; text-align: left; white-space: pre; word-spacing: normal; word-break: normal; line-height: 1.5; tab-size: 4; hyphens: none;">
/// 回调方法 func photoLibraryDidChange(_ changeInstance: PHChange) { // Your code }复制代码
由于这是一组旧的 API,所以就不介绍细节了(比如判断是新增还是删除),感兴趣的朋友可以去了解一下。
新增的 API
PHAccessLevel
在 iOS 14 中新增了权限等级枚举 PHAccessLevel,有两个 case,分别是 “只读” 和 “读写”。
<pre class="prism-token token language-javascript" style="box-sizing: border-box; list-style: inherit; margin: 0.5em 0px; padding: 1em; color: rgb(204, 204, 204); background: rgb(80, 85, 107); border-radius: 3px; overflow: auto; font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace; overflow-wrap: normal; text-align: left; white-space: pre; word-spacing: normal; word-break: normal; line-height: 1.5; tab-size: 4; hyphens: none;">
public enum PHAccessLevel : Int { case addOnly = 1 case readWrite = 2 }复制代码
对应新增了一组获取/查看权限的 API:
<pre class="prism-token token language-javascript" style="box-sizing: border-box; list-style: inherit; margin: 0.5em 0px; padding: 1em; color: rgb(204, 204, 204); background: rgb(80, 85, 107); border-radius: 3px; overflow: auto; font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace; overflow-wrap: normal; text-align: left; white-space: pre; word-spacing: normal; word-break: normal; line-height: 1.5; tab-size: 4; hyphens: none;">
let level: PHAccessLevel = .readWrite // 获取权限 PHPhotoLibrary.requestAuthorization(for: level) { status in // Your code } // 查看权限 let status: PHAuthorizationStatus = PHPhotoLibrary.authorizationStatus(for: level)复制代码
PHAuthorizationStatus
PHAuthorizationStatus 新增了一个 case limited。
<pre class="prism-token token language-javascript" style="box-sizing: border-box; list-style: inherit; margin: 0.5em 0px; padding: 1em; color: rgb(204, 204, 204); background: rgb(80, 85, 107); border-radius: 3px; overflow: auto; font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace; overflow-wrap: normal; text-align: left; white-space: pre; word-spacing: normal; word-break: normal; line-height: 1.5; tab-size: 4; hyphens: none;">
public enum PHAuthorizationStatus : Int { case notDetermined = 0 case restricted = 1 case denied = 2 case authorized = 3 @available(iOS 14, *) case limited = 4 }复制代码
当用户在授权时选择了 “选择照片” 的选项时:
-
使用新 API 将会返回 limited case
-
使用旧 API 将会返回 authorized case
注意: limited case 仅在 PHAccessLevel = .readWrite 时会返回。
总结
新出的 PHPicker 个人觉得一般,如果对 Picker 要求不多的朋友可以考虑使用。然后是新出的 “Limited Photos Library” 模式,这个非常棒,如果有自定义 Picker 的朋友建议跟进一下。