如何使用PHPicker在iOS系统无授权下获取资源

news2024/11/17 20:49:05

e2f1a44d633722a14c1bced7ca102c5a.jpeg

0bf717fec16f39d99ac0000b309c4c32.gif

本文字数:2766

预计阅读时间:18分钟

47efbec559d1c789d730695d8c592f00.png

自iOS14系统开始,苹果加强了用户隐私和安全功能。新增了“Limited Photo Library Access”模式,同时在授权弹窗中增加了“Select Photo”选项。这意味着用户可以在应用程序请求访问相册时选择部分照片供应用程序读取。从应用程序的角度来看,它只能访问到用户选择的这几张照片,无法得知其他照片的存在。然而,并非所有普通用户都能够正确理解这一机制,实际用户反馈中也反映出了一些误解。苹果推荐使用新的PHPicke来解决这个问题。

在本篇文章中,我将详细介绍如何正确使用PHPicker以及何时应该使用PHPicker。我撰写这篇文章的原因是:在尝试使用PHPicker访问资源库时遇到了一些问题。互联网上的许多文章提供的方法都是错误的,从而导致了对PHPicker和iOS权限的一些核心问题的误解。

01

PHPicker是什么?

从iOS14开始,PHPicker是系统提供的Picker ,它允许你从用户的照片库中访问照片和视频。新的PHPicker类不是在UIKit框架中的,而是位于PhotosUI框架中,包括:

  • PHPickerViewController

  • PHPickerConfiguration

  • PHPickerFilter

  • PHPickerResult

当你展现一个PHPickerViewController,它有一个PHPickerConfiguration配置来告诉它要选择多少个媒体项,以及需要选择的媒体类型。通过 PHPickerConfiguration的filter属性,配置可选择的媒体类型,它的选项可以是任意组合:图片、实况照片或视频。通过PHPickerConfiguration的selectionLimit属性来配置用户可以选择的媒体项数量。

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

ce6def66683a838cda5b12acda7b8898.png

02

真的需要用户授权吗?

当用户给予受限访问模式时,如果需要获得未授权的额外资源,网络上很多文章建议你使用PHAsset和PHPicker来获取额外的数据,这样做的问题是,你必须具有访问资源库的权限,这违背了苹果建议的使用PHPicker的初衷:在不请求权限的情况下使用的选择器。

我们来模拟一下流程:你的的应用程序请求访问用户资源库的权限,用户说:“我将只给这个应用程序有限的访问一些照片。” 此时,如果你的应用程序打开PHPicker并显示所有的照片;用户说:“奇怪,我以为我只给有限的访问权限,为什么所有照片都有?”;接下来,用户选择了一张他没有给我们访问权限的照片。应用程序现在需要什么都不做,为了使用PHAsset获得他选择的照片的元数据(metadata),他们必须再次更新他们的权限。用户感到困惑。

所以,如果你的目的非常明确,就是需要用户给予额外的资源授权来获得 PHAsset 对象,应该使用iOS14的新API PHPhotoLibrary.shared().presentLimitedLibraryPicker(from: vc),反之,使用 PHPicker不应该申请获得用户授权,正确的做法是使用 PHPickerViewControllerDelegate返回的NSItemProvider获得元数据(metadata)信息,我将在稍后详细介绍。

03

使用PHPicker的方式

1、错误方式

下面这段代码是网络上广泛被转载的一段错误代码:

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
 }
}

在这段代码中,你使用PHPickerViewController选择了受限制的资源,但是在调用PHAsset.fetchAssets时返回了一个空结果。这是因为fetchAssets方法只能检索用户授权访问的所有资源,而在受限制模式下,只有最近的受限制资源可供访问,所以这种方式是错误的!

2、正确方式

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 {
            // 其他,暂时忽略
        }
    }
}

04

获得图片与图片元数据

通过前一步骤,我们已经知道了资源的类型。接下来通过NSItemProvider的API加载图片内容和获得元数据信息;查阅 NSItemProvider(1)文档,可以看到加载数据,主要提供了下面几种API:

  • loadDataRepresentation:返回资源Data数据

  • loadFileRepresentation:返回资源URL

  • loadObject:指定资源类型返回

这里我推荐使用 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? 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

处理特殊格式图片

如果用户在资源库中选择了一张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
    }        
}

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)")
    }
}

在上面的示例代码中,我们首先创建了一个NSItemProvider对象和一个指定类型标识符。然后,我们使用 loadDataRepresentation(forTypeIdentifier:completionHandler:) 方法加载数据,并返回一个NSProgress对象。我们可以将该对象添加为观察者,并在观察者的回调方法中更新进度条的值。

请注意,NSProgress对象是线程安全的,因此可以在不同的线程中使用。此外,如果你需要在多个地方使用同一个NSProgress对象;

NSProgress对象的fractionCompleted属性,该属性表示任务的完成度,其值在0.0和1.0之间。

06

总结

本文要点包含以下:

  • PHPicker是iOS14开始引入的新组件,它允许在不需要用户授权的情况下访问照片库的所有资源;

  • 使用PHPicker的正确方式是通过PHPickerViewControllerDelegate回调返回的NSItemProvider来获取所选资源,而不是通过PHAsset来获取,后者需要提前获取用户的相册访问授权;

  • 通过NSItemProvider可以判断资源类型,加载资源数据或文件URL,获取图片、视频等多媒体资源;

  • 对于图片,可以通过loadDataRepresentation获取Data,并利用该Data获取图片元数据信息。对于视频,可以通过loadFileRepresentation获取URL,并利用URL获取AVAsset;

  • 通过UTType可以进一步判断特殊格式资源如webp、gif等进行不同处理;

  • 可以通过NSProgress监听资源加载进度;

  • 正确使用PHPicker可以避免引起用户疑惑,提高用户体验,是iOS14访问多媒体资源的推荐方式。

总之,本文详细介绍了在iOS14中如何正确使用PHPicker访问用户选择的部分照片资源,其要点是不需要提前获取授权,通过NSItemProvider处理多媒体资源,这是一种更符合系统设计初衷和提高用户体验的方式。

标注参考链接:

(1)https://developer.apple.com/documentation/foundation/nsitemprovider

(2)https://gist.github.com/lukebrandonfarrell/961a6dbc8367f0ac9cabc89b0052d1fe

其他参考链接:

(1)https://itnext.io/the-right-way-to-use-phpicker-and-retrieve-exif-data-without-requesting-library-permissions-in-336c13f87e3f

(2)https://swiftsenpai.com/development/webp-phpickerviewcontroller/

(3)https://gist.github.com/lukebrandonfarrell/961a6dbc8367f0ac9cabc89b0052d1fe

(4)https://www.jianshu.com/p/5e7aacfa4374

(5)https://developer.apple.com/forums/thread/650902

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1142405.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Unity编辑器扩展之自定义Inspector面板

首先找到的是这个[CustomEditor(typeof(Class), true)],这个东西能够自己绘制在Inspector视图的显示规则,但是!如果这个类被另一个类持有,他就没作用了, 效果图: 1.对CustomClass类编辑自定义面板 2. 对M…

为什么Facebook运营需使用IP代理?

随着互联网的快速发展和全球用户规模的不断增长,Facebook已成为了全球最大的社交媒体平台之一。然而,大批量地运营Facebook账号往往需要借助IP代理这一工具,提高账号的安全性和可靠性,使得运营Facebook更加流畅。那么Facebook为什…

QGIS008:QGIS拓扑检查、修改及验证

摘要:本文介绍使用QGIS拓扑检查器和几何图形检查器检查图层的拓扑错误,修改拓扑错误,并对修改后的图层进行错误验证。 实验数据: 链接:https://pan.baidu.com/s/1Vy2s-KYS-XJevqHNdavv9A?pwdf06o 提取码&#xff1a…

源码角度分析Java 循环中删除数据为什么会报异常

一、源码角度分析Java 循环中删除数据为什么会报异常 相信大家在之前或多或少都知道 Java 中在增强 for中删除数据会抛出:java.util.ConcurrentModificationException 异常,例如:如下所示程序: public class RmTest {public sta…

系统架构师-第10章-软件架构的演化和维护-学习笔记

软件架构一般会经历初始设计、实际使用、修改完善和退化弃阳的过程,其中修改完善的过程实际上就是软件架构的演化和维护过程,演化和维护的H 的就是为f 侦软件能够适应环境的变化而进行的纠铺性修改和完善性修改等。 软件架构演化和定义的关系 演化的重…

069:mapboxGL加载GPX,转换为geojson,显示图形

第069个 点击查看专栏目录 本示例是演示如何在vue+mapbox中加载GPX,转换为geojson,在地图上显示图形。这里面用到了大剑师的gpx2geojson插件,很方便的做了数据的转换。 直接复制下面的 vue+mapbox源代码,操作2分钟即可运行实现效果 文章目录 示例效果所用的gpx文件配置方…

【开源】基于SpringBoot的衣物搭配系统的设计和实现

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、研究内容2.1 衣物档案模块2.2 衣物搭配模块2.3 衣物收藏模块 三、系统设计3.1 用例设计3.2 E-R图设计3.3 数据库设计3.3.1 衣物档案表3.3.2 衣物搭配表3.3.3 衣物收藏表 四、系统实现4.1 登录页4.2 衣物档案模块4.3 衣物搭配模块4.4…

力扣每日一题79:单词搜索

题目描述: 给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。 单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格…

25-什么是事件循环

一、是什么 🍿🍿🍿JavaScript是一门单线程的语言、 意味着同一时间内只能做一件事,但是这并不意味着单线程就是阻塞,而实现单线程非阻塞的方法就是事件循环 在JavaScript中,所有的任务都可以分为 同步任…

C++学习 day--20 new和delete关键字

1、new 和 delete 基本语法 1 )在软件项目开发过程中,我们经常需要动态地分配和撤销内存空间,特别是数据结构中结点的插入与删除。在 C 语言中是利用库函数 malloc 和 free 来分配和撤销内存空间的。C 提供了较简便而功能较强的运算符 …

Screenium for Mac: 重新定义屏幕录制体验

你是否曾经需要记录你的屏幕活动,但却被复杂的软件界面和功能缺失所困扰?现在,Screenium for Mac可以解决你的烦恼。这是一款全新的屏幕录制工具,以其强大的功能和易用性,让你的屏幕录制体验焕然一新。 Screenium for…

三十七、【进阶】验证索引的效率

1、准备工作: 创建一张表,该表中有一千万条数据,名为tb_sku; 2、使用主键查询: select * from tb_stu where id1\G; 3、使用非索引查询: 4、给sn字段创建索引: 在创建过程中,发现…

工业4.0的安全挑战与解决方案

在当今数字化时代,工业4.0已经成为制造业的核心趋势。工业4.0的兴起为生产企业带来了前所未有的效率和灵活性,但与之伴随而来的是一系列的安全挑战。本文将深入探讨工业4.0的安全挑战,并提供一些解决方案,以确保制造业的数字化转型…

FOC系列(二)----继续学习DRV8301芯片

一、 程序框图 跟随上篇博客咱们继续往下看,下面是芯片内部的程序框图: 1.1 BUCK电路 1.2 内部各电源 1.3 SPI通信、栅极驱动器和时序控制器 1.4 MOSFET驱动电路 1.5 电流采样放大电路 数据手册只是给出了这一部分框图,但是没有更加详细的介…

解锁高效检索技能:掌握MySQL索引数据结构的精髓

文章目录 磁盘存储假设每条sql信息为1kb,主键ID为bigint型,一颗高度为2,3,4高度的B树分别可以存储多少行数据?为什么选用B树做索引而不选用二叉树或者B树?1.减少IO次数2.稳定查询3.存储效率高 为什么用 B 树做索引而不用哈希表做…

99. 激光炸弹(二维前缀和)

题目&#xff1a; 99. 激光炸弹 - AcWing题库 思路&#xff1a; 1.矩形/正方形求最值--->二维前缀和 2.注意&#xff1a;此题不可开两个数组&#xff0c;空间会爆&#xff0c;前缀和数组与原数据数组共用一个数组。 代码&#xff1a; #include <cstring> #inc…

如何保护您的个人信息数据库安全?

保护个人信息数据库安全是一项至关重要的任务&#xff0c;因为我们的个人信息可能包含许多敏感数据&#xff0c;如果泄露可能会导致各种问题&#xff0c;例如身份盗窃、网络诈骗等。以下是一些建议&#xff0c;可以帮助您保护您的个人信息数据库安全&#xff1a; 加密数据&…

Redis进军磁盘存储

目录 1、对抗价格优势&#xff1a;纳入磁盘&#xff0c;降低成本&#xff1f; 2、Redis的野心&#xff1a;无敌是多么寂寞&#xff0c;所以我们要开新地图 3、开发者异议&#xff1a;他们正在偏离我们选择Redis的初衷 4、结语&#xff1a;性能为王&#xff0c;但绝不甘于只…

公网远程访问macOS本地web服务器

# 公网访问macOS本地web服务器【内网穿透】 文章目录 1. 启动Apache服务器2. 公网访问本地web服务2.1 本地安装配置cpolar2.2 创建隧道2.3 测试访问公网地址3. 配置固定二级子域名3.1 保留一个二级子域名3.2 配置二级子域名4. 测试访问公网固定二级子域名 以macOS自带的Apache…

mac 查看GPU使用

首先搜索活动监视器 然后 点击窗口->gpu历史记录 记住不是立马出结果&#xff0c;而是 需要等半分钟左右的