自iOS14系统开始,何使获苹果加强了用户隐私和安全功能。系统新增了“Limited Photo Library Access”模式,无授同时在授权弹窗中增加了“Select Photo”选项。权下这意味着用户可以在应用程序请求访问相册时选择部分照片供应用程序读取。资源从应用程序的何使获角度来看,它只能访问到用户选择的系统这几张照片,无法得知其他照片的无授存在。然而,权下并非所有普通用户都能够正确理解这一机制,资源实际用户反馈中也反映出了一些误解。苹果推荐使用新的PHPicke来解决这个问题。
在本篇文章中,我将详细介绍如何正确使用PHPicker以及何时应该使用PHPicker。我撰写这篇文章的原因是:在尝试使用PHPicker访问资源库时遇到了一些问题。互联网上的许多文章提供的方法都是错误的,从而导致了对PHPicker和iOS权限的一些核心问题的误解。
从iOS14开始,PHPicker是系统提供的Picker ,它允许你从用户的照片库中访问照片和视频。新的PHPicker类不是在UIKit框架中的,而是位于PhotosUI框架中,包括:
当你展现一个PHPickerViewController,它有一个PHPickerConfiguration配置来告诉它要选择多少个媒体项,以及需要选择的媒体类型。通过 PHPickerConfiguration的filter属性,配置可选择的媒体类型,它的选项可以是任意组合:图片、实况照片或视频。通过PHPickerConfiguration的selectionLimit属性来配置用户可以选择的媒体项数量。
let photoLibrary = PHPhotoLibrary.shared()var config = PHPickerConfiguration(photoLibrary: photoLibrary) let selectedCount = self.albumViewModel.selectArray.countlet limited = min(4-selectedCount, 4)config.selectionLimit = (type == .pic ? limited : 1)config.filter = (type == .pic ? .images : .videos)let pickerViewController = PHPickerViewController(configuration: config)pickerViewController.delegate = selfself.viewController?.present(pickerViewController, ani
图片
当用户给予受限访问模式时,如果需要获得未授权的额外资源,网络上很多文章建议你使用PHAsset和PHPicker来获取额外的数据,这样做的问题是,你必须具有访问资源库的权限,这违背了苹果建议的使用PHPicker的初衷:在不请求权限的情况下使用的选择器。
我们来模拟一下流程:你的的应用程序请求访问用户资源库的权限,用户说:“我将只给这个应用程序有限的访问一些照片。” 此时,如果你的应用程序打开PHPicker并显示所有的照片;用户说:“奇怪,我以为我只给有限的访问权限,为什么所有照片都有?”;接下来,用户选择了一张他没有给我们访问权限的照片。应用程序现在需要什么都不做,为了使用PHAsset获得他选择的照片的元数据(metadata),他们必须再次更新他们的权限。用户感到困惑。
所以,如果你的目的非常明确,就是需要用户给予额外的资源授权来获得 PHAsset 对象,应该使用iOS14的新API PHPhotoLibrary.shared().presentLimitedLibraryPicker(from: vc),反之,使用 PHPicker不应该申请获得用户授权,正确的做法是使用 PHPickerViewControllerDelegate返回的NSItemProvider获得元数据(metadata)信息,我将在稍后详细介绍。
下面这段代码是网络上广泛被转载的一段错误代码:
import UIKitimport PhotosUIclass 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 }}
在这段代码中,你使用PHPickerViewController选择了受限制的资源,但是在调用PHAsset.fetchAssets时返回了一个空结果。这是因为fetchAssets方法只能检索用户授权访问的所有资源,而在受限制模式下,只有最近的受限制资源可供访问,所以这种方式是错误的!
PHAsset不应该与PHPicker一起使用,这不是使用PHPickeri的正确方法!应该使用NSItemProvider。NSItemProvider是一个项目提供程序,用于在拖放或复制/粘贴活动期间在进程之间传输数据或文件,或者从主机应用程序到应用程序扩展。使用itemProvider,可以读取对象的类型,并根据它是照片、视频还是其他内容来处理它。比较合适的方式如下所示:
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 { // 其他,暂时忽略 } }}
通过前一步骤,我们已经知道了资源的类型。接下来通过NSItemProvider的API加载图片内容和获得元数据信息;查阅 NSItemProvider(1)文档,可以看到加载数据,主要提供了下面几种API:
这里我推荐使用 loadDataRepresentation,返回Data数据,方便下一步获得元数据信息。
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 { // 其他,暂时忽略 } }}
在处理业务model转换函数中,由于Data类型很容易转换成UIImage,并且通过将Data转换为CFData 类型,可以通过系统预设的key/value键值对获得元数据信息,
let imgSrc = CGImageSourceCreateWithData(data, options as CFDictionary)let metadata = CGImageSourceCopyPropertiesAtIndex(imgSrc, 0, options as CFDictionary)colorModel = metadata[kCGImagePropertyColorModel] as? StringpixelWidth = 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}
如果用户在资源库中选择了一张WebP格式图片或者GIF动图,由于展示所需代码和形式均不同,所以需要特别区分,那么如何来区别处理呢?
我们可以通过UTType来具体区分不同类型,UTType是Uniform Type Identifier的缩写,用于标识特定类型的文件或数据。在macOS和iOS等操作系统中,UTType通常用于识别文件类型、将文件分组到合适的应用程序中、在不同应用程序之间共享数据等。
UTType由两部分组成:类型标识符(type identifier)和类型标签(type tag)。类型标识符是一串唯一的字符串,用于标识特定类型的文件或数据,通常采用反向DNS风格的命名方式,如com.adobe.pdf、public.image等。类型标签是一个可选的字符串,用于描述特定类型的文件或数据,例如"PDF document"或"JPEG image"等。
if(itemProvider.hasItemConformingToTypeIdentifier(UTType.image.identifier)) { // 图片处理 // 判断webp if itemProvider.hasItemConformingToTypeIdentifier(UTType.webP.identifier){ //处理webp } //判断GIF if itemProvider.hasItemConformingToTypeIdentifier(UTType.gif.identifier){ //处理GIF } }
获得视频时,推荐使用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 { // 其他,暂时忽略 } }}
当获取的资源文件较大时,我们需要获得加载数据的进度,此时可以使用 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("fractinotallow=\(self.progress?.fractionCompleted)") }}
在上面的示例代码中,我们首先创建了一个NSItemProvider对象和一个指定类型标识符。然后,我们使用 loadDataRepresentation(forTypeIdentifier:completionHandler:) 方法加载数据,并返回一个NSProgress对象。我们可以将该对象添加为观察者,并在观察者的回调方法中更新进度条的值。
请注意,NSProgress对象是线程安全的,因此可以在不同的线程中使用。此外,如果你需要在多个地方使用同一个NSProgress对象;
NSProgress对象的fractionCompleted属性,该属性表示任务的完成度,其值在0.0和1.0之间。
本文要点包含以下:
总之,本文详细介绍了在iOS14中如何正确使用PHPicker访问用户选择的部分照片资源,其要点是不需要提前获取授权,通过NSItemProvider处理多媒体资源,这是一种更符合系统设计初衷和提高用户体验的方式。
(1)https://developer.apple.com/documentation/foundation/nsitemprovider
(2)https://gist.github.com/lukebrandonfarrell/961a6dbc8367f0ac9cabc89b0052d1fe
责任编辑:武晓燕 来源: 搜狐技术产品 PHPickeriOS系统(责任编辑:时尚)
荣盛发展大股东质押公司7599万股股份 占公司总股本比例的1.75%
图灵机就是深度学习最热循环神经网络RNN?1996年论文就已证明!
富创精密(688409):2023年11月14日共有1笔大宗交易,折价率为1.0011%