iOS14中的PHPicker

时间:2022-07-27
本文章向大家介绍iOS14中的PHPicker,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

PHPicker

iOS 14 中系统新增了一个图片选择器 PHPicker,官方建议使用 PHPicker 来替代原有的 API 进行图片选择,下面我们来看看 PHPicker 的优点:

  • 支持多选
  • 支持搜索
  • 独立的进程
  • 内置隐私
    • 不需要直接访问用户相册
    • 不会弹出访问相册提示
    • 仅提供用户选择的照片和视频(App 无法获取其他照片)

如何调用 PHPicker

我们先来看下 PHPicker 的流程图,首先声明 PHPickerConfiguration,进行配置,再传给 PHPickerViewController,完成调用环节,代码如下:

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)复制代码

处理 PHPicker 的回调

PHPicker 的代理方法只有一个,声明如下:

@available(iOS 14, *)
public protocol PHPickerViewControllerDelegate : AnyObject {
    func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult])
}复制代码

注意: 取消选择也会触发代理方法,会返回空的 results

如何获取照片

PHPicker 获取图片的方法还是比较简单的,代码如下:

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 以及视频中都没有介绍,这也是我迟迟没有写文章的原因,因为之前我也不知道怎么获取,那么下面让我们一起来看下怎么获取视频。

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

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 读取。

当用户选择了 limited 模式后,系统将在 App 每次启动后首次触发相册时弹出提示,允许用户修改需要授权给 App 的照片。iOS开发交流技术群:[563513413](https://jq.qq.com/?_wv=1027&k=lzJejkSl),不管你是大牛还是小白都欢迎入驻

当然这个弹窗是可以关闭的,如果你希望手动控制 PHPickerController 弹出的时机也是有办法的。

我们需要在 Info.plist 中添加 PHPhotoLibraryPreventAutomaticLimitedAccessAlert 字段,并设置为 YES,设置后系统将不再弹出访问提示。

然后我们可以在合适的时机调用以下这个 API 来推出 PHPickerController

let viewController = self
PHPhotoLibrary.shared().presentLimitedLibraryPicker(from: viewController)复制代码

我们可以看到,当用户选择 limited 模式后,底部出现了一段提示:“无法查看相册全部照片,点击选择更多照片”。当点击这个提示后,将会推出 PHPickerController,此时用户可以修改授权给 App 的照片。同时我们会监听相册的变化,当用户修改授权的照片后,会立即刷新相册,用户可以继续进行选择照片的流程。

监听相册变化

配合手动调用 PHPickerController,我们还需要监听用户添加/删除了哪些照片。

注意: 这组 API 并不是新出的,从 iOS 8 开始就支持了。

let viewController = self
// 开始监听
PHPhotoLibrary.shared().register(viewController) 
// 结束监听
PHPhotoLibrary.shared().unregisterChangeObserver(viewController)复制代码

处理监听回调:

/// 回调方法
func photoLibraryDidChange(_ changeInstance: PHChange) {
    // Your code
}复制代码

由于这是一组旧的 API,所以就不介绍细节了(比如判断是新增还是删除),感兴趣的朋友可以去了解一下。

新增的 API

PHAccessLevel

在 iOS 14 中新增了权限等级枚举 PHAccessLevel,有两个 case,分别是 “只读” 和 “读写”。

public enum PHAccessLevel : Int {
    case addOnly = 1
    case readWrite = 2
}复制代码

对应新增了一组获取/查看权限的 API:

let level: PHAccessLevel = .readWrite
// 获取权限
PHPhotoLibrary.requestAuthorization(for: level) { status in
  // Your code
}
// 查看权限
let status: PHAuthorizationStatus = PHPhotoLibrary.authorizationStatus(for: level)复制代码

PHAuthorizationStatus

PHAuthorizationStatus 新增了一个 case limited

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 的朋友建议跟进一下。