iOS14中的PHPicker,你是否了解?

1,545 阅读6分钟

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, &quot;Andale Mono&quot;, &quot;Ubuntu Mono&quot;, 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, &quot;Andale Mono&quot;, &quot;Ubuntu Mono&quot;, 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, &quot;Andale Mono&quot;, &quot;Ubuntu Mono&quot;, 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, &quot;Andale Mono&quot;, &quot;Ubuntu Mono&quot;, 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, &quot;Andale Mono&quot;, &quot;Ubuntu Mono&quot;, 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, &quot;Andale Mono&quot;, &quot;Ubuntu Mono&quot;, 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, &quot;Andale Mono&quot;, &quot;Ubuntu Mono&quot;, 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, &quot;Andale Mono&quot;, &quot;Ubuntu Mono&quot;, 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, &quot;Andale Mono&quot;, &quot;Ubuntu Mono&quot;, 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, &quot;Andale Mono&quot;, &quot;Ubuntu Mono&quot;, 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, &quot;Andale Mono&quot;, &quot;Ubuntu Mono&quot;, 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 的朋友建议跟进一下。