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

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.mfbz.cn/a/107581.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

网络协议--TCP连接的建立与终止

18.1 引言 TCP是一个面向连接的协议。无论哪一方向另一方发送数据之前,都必须先在双方之间建立一条连接。本章将详细讨论一个TCP连接是如何建立的以及通信结束后是如何终止的。 这种两端间连接的建立与无连接协议如UDP不同。我们在第11章看到一端使用UDP向另一端发…

Spring Boot进阶(94):从入门到精通:Spring Boot和Prometheus监控系统的完美结合

📣前言 随着云原生技术的发展,监控和度量也成为了不可或缺的一部分。Prometheus 是一款最近比较流行的开源时间序列数据库,同时也是一种监控方案。它具有极其灵活的查询语言、自身的数据采集和存储机制以及易于集成的特点。而 Spring Boot 是…

使用Python将PDF转为图片

将PDF转为图片能方便我们将文档内容上传至社交媒体平台进行分享。此外,转换为图片后,还可以对图像进行进一步的裁剪、调整大小或添加标记等操作。 用Python将PDF文件转JPG/ PNG图片可能是大家在一些项目中会遇到的需求,下面将详细介绍如何使用…

JAVA:集合框架常见的面试题和答案

1、List接口的常见实现类有哪些? 答: 常见的List接口实现类包括: ArrayList: 基于动态数组实现的List,支持快速随机访问。LinkedList: 基于链表实现的List,支持快速的插入和删除操作。Vector: 一个线程安全的动态数组…

【开源】基于SpringBoot的天然气工程业务管理系统的设计和实现

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块三、使用角色3.1 施工人员3.2 管理员 四、数据库设计4.1 用户表4.2 分公司表4.3 角色表4.4 数据字典表4.5 工程项目表4.6 使用材料表4.7 使用材料领用表4.8 整体E-R图 五、系统展示六、核心代码6.1 查询工程项目6.2 工程物资…

泛积木-低代码 使用攻略

文档首发于 泛积木-低代码 使用攻略 我们以大纲的方式(总体把握)讲述如何高效、便捷使用 泛积木-低代码。 权限 首先说下权限,在 系统设置 / 权限设置 菜单内,我们可以新增调整项目内的权限,默认拥有管理员和成员两…

Python数据挖掘:入门、进阶与实用案例分析——基于非侵入式负荷检测与分解的电力数据挖掘

文章目录 摘要01 案例背景02 分析目标03 分析过程04 数据准备05 属性构造06 模型训练07 性能度量08 推荐阅读赠书活动 摘要 本案例将根据已收集到的电力数据,深度挖掘各电力设备的电流、电压和功率等情况,分析各电力设备的实际用电量,进而为电…

仓库管理系统源代码集合,带图片展示和网站演示

目录 1、ModernWMS2、GreaterWMS3、kopSoftWMS4、SwebWMS5、若依wms6、jeewms 1、ModernWMS 体验地址:https://wmsonline.ikeyly.com 简易完整的仓库管理系统 该库存管理系统是,我们从多年ERP系统研发中总结出来的一套针对小型物流仓储供应链流程。 简…

《C和指针》笔记34:字符串函数

文章目录 1. 获取字符串长度strlen 2. 复制字符串strcpystrncpy 3. 拼接字符串strcatstrncat 4. 字符串比较strcmpstrncmp 5. 查找字符strchr和strrchr:查找一个字符strpbrk:查找任何几个字符strstr:查找一个子串strspn和strcspn:…

Nginx的进程结构实例演示

可以参考《Ubuntu 20.04使用源码安装nginx 1.14.0》安装nginx 1.14.0。 nginx.conf文件中worker_processes 2;这条语句表明启动两个worker进程。 sudo /nginx/sbin/nginx -c /nginx/conf/nginx.conf开启nginx。 ps -ef | grep nginx看一下进程情况。 sudo /nginx/sbin/ng…

Mathtype使用指南01:下载与安装

目录 介绍: 安装 介绍: MathType 是一款广泛用于数学和科学文档创建的强大数学编辑工具。它允许用户轻松地在各种文档类型中插入数学方程、符号和公式,是学术界、工程领域、出版界和教育机构中的专业人士常用的工具。下面是关于 MathType 的…

设计模式:桥接模式(C#、JAVA、JavaScript、C++、Python、Go、PHP)

上一篇《适配器模式》 下一篇《装饰器模式》 简介: 桥接模式,它是一种结构型设计模式,它的主要目的是将抽象部分与具体实现部分分离,使它们都可以独立地变化。…

SQL server 代理服务启动和查看

设置重启 使用管理员权限登录到运行 SQL Server 代理服务的计算机。 打开 Windows 服务管理器。可以通过按下 Windows 键 R,然后键入 "services.msc" 并按 Enter 来打开服务管理器。 在服务列表中,找到 "SQL Server Agent" 服务&…

网络协议--TFTP:简单文件传送协议

15.1 引言 TFTP(Trivial File Transfer Protocol)即简单文件传送协议,最初打算用于引导无盘系统(通常是工作站或X终端)。和将在第27章介绍的使用TCP的文件传送协议(FTP)不同,为了保持简单和短小&#xff0…

无需更换vue-cli 脚手架 uniapp-搭建项目-H5-低版本安卓IOS兼容问题(白屏)(接口请求异常)

✨求关注~ 💻博客:www.protaos.com I. 简介 A. UniApp项目概述 B. 白屏和接口请求异常问题的背景 II. 白屏问题 A. 问题描述 1、uniapp 打包H5内嵌入APP内、低版本手机系统访问白屏问题 B. 问题根本原因 1、低版本手机系统 自带的webview内核不支持ES6语…

Java练习题2020-4

小明今天收了N个鸡蛋&#xff0c;每个鸡蛋各有重量&#xff0c;现在小明想找M个重量差距最小的鸡蛋摆成一盒出售&#xff0c;输出符合条件的最重一盒鸡蛋的总重量 输入说明&#xff1a;第一行&#xff0c;鸡蛋个数N(N<1000) 每盒个数M(M<N)&#xff1b;第二行&#xff0…

Jtti:Apache服务的反向代理及负载均衡怎么配置

配置Apache服务的反向代理和负载均衡可以帮助您分散负载并提高应用程序的可用性和性能。下面是一些通用的步骤&#xff0c;以配置Apache反向代理和负载均衡。 1. 安装和配置Apache&#xff1a; 确保您已经安装了Apache HTTP服务器。通常&#xff0c;Apache的配置文件位于/etc…

Linux进程控制(一)

前言&#xff1a;Linux进程控制是指在Linux操作系统中&#xff0c;对进程的创建、运行、管理和终止等方面进行控制的一系列机制和技术。Linux作为一个多任务操作系统&#xff0c;能够同时运行多个进程任务的执行&#xff0c;继前面我们对Linux进程创建的学习之后&#xff0c;今…

CSP-J 2023 第二轮认证入门级(含答案)

一。题目 二。答案 T1 ⼩苹果&#xff08;apple&#xff09; 每⼀轮拿掉的苹果数量为 。模拟拿苹果的过程&#xff0c;每⼀轮中令 &#xff0c;当 时最后⼀个苹果会被拿掉。 时间复杂度为对数。 #include <iostream> using namespace std; int n; int ans1, ans2; boo…

上海高考语文命题趋势和备考建议?附1990年-2023年高考语文真题和答案资源

虽然语文是我们的母语&#xff0c;但是语文从小到大都是我们学习的重点&#xff0c;更是难点&#xff0c;分值也是最高的科目之一。甚至很多时候&#xff0c;语文科目的分值差会带来最终的分值差。综观各个省市的高考状元&#xff0c;基本上语文科目都在130分以上&#xff08;满…
最新文章