本文字数:2766字
预计阅读时间:18分钟
01
PHPicker是什么?
let photoLibrary = PHPhotoLibrary.shared()
var config = PHPickerConfiguration(photoLibrary: photoLibrary)
let selectedCount = self.albumViewModel.selectArray.count
let limited = min(4-selectedCount, 4)
config.selectionLimit = (type == .pic ? limited : 1)
config.filter = (type == .pic ? .images : .videos)
let pickerViewController = PHPickerViewController(configuration: config)
pickerViewController.delegate = self
self.viewController?.present(pickerViewController, ani
02
真的需要用户授权吗?
03
使用PHPicker的方式
下面这段代码是网络上广泛被转载的一段错误代码:
import UIKit
import PhotosUI
class PhotoKitPickerViewController: UIViewController, PHPickerViewControllerDelegate {
@IBAction func presentPicker(_ sender: Any) {
let photoLibrary = PHPhotoLibrary.shared()
let configuration = PHPickerConfiguration(photoLibrary: photoLibrary)
let picker = PHPickerViewController(configuration: configuration)
picker.delegate = self
present(picker, animated: true)
}
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
picker.dismiss(animated: true)
let identifiers = results.compactMap(\.assetIdentifier)
let fetchResult = PHAsset.fetchAssets(withLocalIdentifiers: identifiers, options: nil)
// TODO: Do something with the fetch result if you have Photos Library access
}
}
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
picker.dismiss(animated: true)
for result in results {
let itemProvider: NSItemProvider = result.itemProvider;
if(itemProvider.hasItemConformingToTypeIdentifier(UTType.image.identifier)) {
// 图片处理
} else if(itemProvider.hasItemConformingToTypeIdentifier(UTType.movie.identifier)) {
// 视频处理
} else {
// 其他,暂时忽略
}
}
}
04
获得图片与图片元数据
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
picker.dismiss(animated: true)
for result in results {
let itemProvider: NSItemProvider = result.itemProvider;
if(itemProvider.hasItemConformingToTypeIdentifier(UTType.image.identifier)) {
// 图片处理
itemProvider.loadDataRepresentation(forTypeIdentifier: UTType.image.identifier) { data, error in
//处理业务model转换
if let model = self.createPhotoResourcesModel(data: data, assetIdentifier: assetIdentifier){
self.albumViewModel.selectArrayAddObject(model)
DispatchQueue.main.async {
//更新UI
}
}
}
} else if(itemProvider.hasItemConformingToTypeIdentifier(UTType.movie.identifier)) {
// 视频处理
} else {
// 其他,暂时忽略
}
}
}
let imgSrc = CGImageSourceCreateWithData(data, options as CFDictionary)
let metadata = CGImageSourceCopyPropertiesAtIndex(imgSrc, 0, options as CFDictionary)
colorModel = metadata[kCGImagePropertyColorModel] as? String
pixelWidth = metadata[kCGImagePropertyPixelWidth] as? Double;
pixelHeight = metadata[kCGImagePropertyPixelHeight] as? Double;
这里我们使用了Github上开源的 ExifData(2) 代码,完整实现了所有字段的获取封装,使用起来非常方便。
func createPhotoResourcesModel(data:Data?,
assetIdentifier:String?) -> SNSResourcesModel? {
guard let imageData = data, let uiimage = UIImage(data: imageData) else {
return nil
}
let model = SNSResourcesModel()
model.assetLocalIdentifier = assetIdentifier
model.assetType = .photo
model.assetSource = .album
model.originImage = uiimage
model.bigPreviewImage = uiimage
let exifData = ExifData(data: imageData)
model.pixelWidth = Int(exifData.pixelWidth ?? 0)
model.pixelHeight = Int(exifData.pixelHeight ?? 0)
if imageData.imageType == .GIF{
model.isGif = true
model.gifData = imageData
}
return model
}
05
处理特殊格式图片
if(itemProvider.hasItemConformingToTypeIdentifier(UTType.image.identifier)) {
// 图片处理
// 判断webp
if itemProvider.hasItemConformingToTypeIdentifier(UTType.webP.identifier){
//处理webp
}
//判断GIF
if itemProvider.hasItemConformingToTypeIdentifier(UTType.gif.identifier){
//处理GIF
}
}
06
获得视频
获得视频时,推荐使用loadFileRepresentation,返回URL,通过URL可以获得 AVAsset。
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
picker.dismiss(animated: true)
for result in results {
let itemProvider: NSItemProvider = result.itemProvider;
if(itemProvider.hasItemConformingToTypeIdentifier(UTType.image.identifier)) {
// 图片处理
} else if(itemProvider.hasItemConformingToTypeIdentifier(UTType.movie.identifier)) {
// 视频处理
itemProvider.loadFileRepresentation(forTypeIdentifier: UTType.movie.identifier) { url, error in
//业务model转换
if let model = self.createVideoResourcesModel(url: url, assetIdentifier: assetIdentifier){
self.albumViewModel.selectArrayAddObject(model)
DispatchQueue.main.async {
//展示UI
}
}
}
} else {
// 其他,暂时忽略
}
}
}
06
获取加载进度
当获取的资源文件较大时,我们需要获得加载数据的进度,此时可以使用 NSItemProvider的加载数据函数提供的返回值NSProgress对象。
var progress:Progress?
progress = itemProvider.loadDataRepresentation(forTypeIdentifier: UTType.image.identifier) { data, error in
//...
}
//添加观察者
progress?.addObserver(self, forKeyPath: "fractionCompleted", options: [.new], context: nil)
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "fractionCompleted" {
print("fractionCompleted=\(self.progress?.fractionCompleted)")
}
}
06
总结
本文要点包含以下:
PHPicker是iOS14开始引入的新组件,它允许在不需要用户授权的情况下访问照片库的所有资源;
使用PHPicker的正确方式是通过PHPickerViewControllerDelegate回调返回的NSItemProvider来获取所选资源,而不是通过PHAsset来获取,后者需要提前获取用户的相册访问授权;
通过NSItemProvider可以判断资源类型,加载资源数据或文件URL,获取图片、视频等多媒体资源;
对于图片,可以通过loadDataRepresentation获取Data,并利用该Data获取图片元数据信息。对于视频,可以通过loadFileRepresentation获取URL,并利用URL获取AVAsset;
通过UTType可以进一步判断特殊格式资源如webp、gif等进行不同处理;
可以通过NSProgress监听资源加载进度;
正确使用PHPicker可以避免引起用户疑惑,提高用户体验,是iOS14访问多媒体资源的推荐方式。
(1)https://developer.apple.com/documentation/foundation/nsitemprovider
(2)https://gist.github.com/lukebrandonfarrell/961a6dbc8367f0ac9cabc89b0052d1fe