当前位置: 首页 > news >正文

Photos框架 - 自定义媒体资源选择器(数据部分)

引言

在iOS开发中,系统已经为我们提供了多种便捷的媒体资源选择方式,如UIImagePickerController和PHPickerViewController。这些方式不仅使用方便、界面友好,而且我们完全不需要担心性能和稳定性问题,因为它们是由系统提供的,经过充分测试和优化。

然而,在实际开发过程中,为了使我们的APP更加独特,界面更加新颖,设计团队往往会提出个性化的媒体资源选择页面的需求。这时候,我们就需要放弃系统提供的方案,转而创建自定义的媒体资源选择器。本文将介绍如何使用Photos框架来自定义媒体资源选择器,以满足特定的设计和功能需求。

AssetsLibrary -> Photos

在iOS 8之前,开发者主要使用AssetsLibrary框架来访问和管理用户的照片和视频。然而,从iOS 8开始,Apple引入了全新的Photos框架,并逐步弃用AssetsLibrary

自iOS 9起,AssetsLibrary被正式标记为弃用,Apple强烈建议开发者迁移到Photos框架。Photos框架不仅提供了更高效的性能和更丰富的功能,还为开发者提供了更强大的工具来管理和操作用户的媒体资源。

通过Photos框架,开发者可以更轻松地获取媒体元数据、编辑照片、创建自定义相册,以及实现更多自定义功能。这一过渡标志着iOS媒体管理能力的重大提升,为开发者提供了更广泛的可能性和更强大的控制力。

下面我们就使用Photos框架,来创建一个初级的媒体资源选择器,之后的博客中,我们再不停的来完善它的功能。

创建媒体选择器

我打算把它分成数据和UI两部分来实现这个媒体选择器,本篇博客我们就先从数据部分说起。

媒体数据读取

1.创建配置信息

在媒体资源读取时有很多数据我们可以进行任意配置,比如读取的媒体类型、读取的视频最大时长、获取缩略图尺寸,图片缓存个数等等,为此我们创建了一个名为PHMediaConfig的类,代码如下:

import UIKit
import Photosenum PHMediaType {/// 图片case image/// 视频case video/// 图片和视频case all
}class PHMediaConfig: NSObject {/// 获取资源类型(默认视频和图片)var mediaType: PHMediaType = .all/// 缩略图缓存数量var thumbnailCacheCount: Int = 40/// 大图缓存数量var originalImageCacheCount: Int = 10/// 获取视频的时长最大值var videoMaxDuration: TimeInterval = 60/// 是否直接加载原图var isLoadOriginalImage: Bool = false/// 可选图片最大数量var maxSelectedImageCount: Int = 9/// 缩略图尺寸var thumbnailSize: CGSize = CGSize(width: 200, height: 200)}

里定义了很多配置信息,并且也都设置了初始值。

2.创建媒体资源管理类

创建一个继承自NSObjct名为PHMediaManager的类,用来读取媒体资源数据,获取缩略,原图等等一切和数据相关的内容,并通过初始化方法传入配置信息,代码如下:

class PHMediaManager: NSObject {/// 缩略图缓存private var thumbnailCache = NSCache<NSString, UIImage>()/// 原图缓存private var originalImageCache = NSCache<NSString, UIImage>()/// 配置private var config: PHMediaConfig!init(config: PHMediaConfig = PHMediaConfig()) {super.init()self.config = configthumbnailCache.countLimit = config.thumbnailCacheCountoriginalImageCache.countLimit = config.originalImageCacheCount}....
}

除此之外,我们还定义了两个缓存表,稍后的代码中会使用到它们。

3.获取媒体库权限

苹果对隐私权限的申请非常重视,所以在获取媒体资源前一定要检查权限和申请权限,并给用户友好的提示,包括infoplist文件内的文案也需要认真填写,表明申请权限的用途。

检查和申请权限的代码如下:

    /// 查看相册权限func checkPhotoLibraryAuthorization() -> Bool {let status = PHPhotoLibrary.authorizationStatus()if status == .authorized {return true} else {return false}}
    /// 查看并获取相册权限/// - Parameter completion: 回调func requestPhotoLibraryAuthorization(completion: @escaping (Bool) -> Void) {PHPhotoLibrary.requestAuthorization { status inif status == .authorized {print("获取相册权限成功")completion(true)} else {print("获取相册权限失败")completion(false)}}}
4.获取媒体资源

权限申请通过后,就可以开始获取媒体资源了,这时候有两个方案可以供我们选择:

方案一:在读取资源时,通过PHAsset直接读取缩略图构建模型数组。

如果采用方案1的话,在渲染列表时,我们就可以直接使用UIImage进行渲染,页面反应很快,用户体验会很好。

但是呢预先加载所有的缩略图这样会占用很大的内存,尤其是相册资源比较多的情况甚至可能会导致崩溃,这样的话我们就需要手动控制一次加载资源的数量。

方案二:在读取资源时,只保存PHAsset。

这个方案呢,我们到不需要考虑内存的问题,因为只有在现实的时候才会加载缩略图,显示完成之后就会自动被释放,但是这就会有新的问题,每次图片都是重新加载,可能会使得页面不流畅,影响用户体验。这样的话我们就需要自己来创建和管理缓存来提升用户体验。

我们来采取方案二 + 自定义缓存的方式来读取媒体资源,这样的话我们的数据模型只需要保存PHAsset就可以了,我们先来看一下自定义数据模型的代码:

import UIKit
import Photosclass PHMediaModel: NSObject {/// 资源var asset: PHAsset?/// 是否选中var isSelected: Bool = false/// 资源标识var identifier: String?/// 类型var mediaType: PHAssetMediaType {get {return asset?.mediaType ?? .unknown}}/// 视频时长var videoDuration: TimeInterval {get {return asset?.duration ?? 0}}
}

除了PHAsset以外,还定义了一些选中状态已经视频时长等数据,稍后我们会使用到它们。

下面就开始读取媒体资源数据,构建自定义数据模型:

    /// 获取相册资源/// - Parameters:func fetchLocalAlbums(completion: @escaping ([PHMediaModel]) -> Void) {self.fetchLocalAlbums(type: config.mediaType, completion: completion)}/// 获取本地相册资源/// - Parameters:///  - type: 类型///  - completion: 回调private func fetchLocalAlbums(type: PHMediaType, completion: @escaping ([PHMediaModel]) -> Void) {let options = PHFetchOptions()options.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]if type == .image {options.predicate = NSPredicate(format: "mediaType = %d", PHAssetMediaType.image.rawValue)} else if type == .video {options.predicate = NSPredicate(format: "mediaType = %d", PHAssetMediaType.video.rawValue)} else {options.predicate = NSPredicate(format: "mediaType = %d || mediaType = %d", PHAssetMediaType.image.rawValue, PHAssetMediaType.video.rawValue)}let fetchResult = PHAsset.fetchAssets(with: options)var assets = [PHMediaModel]()fetchResult.enumerateObjects { asset, index, stop inif asset.mediaType == .image {let model = PHMediaModel()model.asset = assetmodel.identifier = asset.localIdentifierassets.append(model)} else if asset.mediaType == .video {if asset.duration <= self.config.videoMaxDuration {let model = PHMediaModel()model.asset = assetmodel.identifier = asset.localIdentifierassets.append(model)}}}completion(assets)}

通常情况下,我们只关心图片类型和视频类型的数据,并且根据视频的时长还进行了进一步的过滤。

5.获取资源缩略图

另外我们还单独定义了一个读取资源缩略图的方法,并且在这个方法里面使用了缩略图缓存,代码如下:

    /// 获取缩略图/// - Parameters:/// - asset: 资源/// - size: 尺寸/// - completion: 回调func fetchThumbnail(asset: PHAsset, size: CGSize? = nil, completion: @escaping (UIImage?) -> Void) {if let size = size {config.thumbnailSize = size}let key = asset.localIdentifier as NSStringif let image = thumbnailCache.object(forKey: key) {completion(image)} else {let options = PHImageRequestOptions()options.isSynchronous = falseoptions.resizeMode = .fastoptions.deliveryMode = .opportunisticoptions.isNetworkAccessAllowed = truePHImageManager.default().requestImage(for: asset, targetSize: config.thumbnailSize, contentMode: .aspectFill, options: options) { image, info inif let image = image {self.thumbnailCache.setObject(image, forKey: key)completion(image)} else {completion(nil)}}}}
6.获取图片资源原图

除了缩略图之外,还需要读取图片原图用来图片单张预览,代码如下:

    /// 获取原图/// - Parameters:/// - asset: 资源/// - completion: 回调func fetchOriginalImage(asset: PHAsset, completion: @escaping (UIImage?) -> Void) {let key = asset.localIdentifier as NSStringif let image = originalImageCache.object(forKey: key) {completion(image)} else {let options = PHImageRequestOptions()options.isSynchronous = falseoptions.resizeMode = .fastoptions.deliveryMode = .opportunisticoptions.isNetworkAccessAllowed = truePHImageManager.default().requestImage(for: asset, targetSize: PHImageManagerMaximumSize, contentMode: .aspectFill, options: options) { image, info inif let image = image {self.originalImageCache.setObject(image, forKey: key)completion(image)} else {completion(nil)}}}}
7.获取视频原数据

获取完图片数据,视频也需要原始的预览数据,毕竟我们上传时不能只上传一个缩略图,代码如下:

    /// 获取视频原数据/// - Parameters:/// - asset: 资源/// - completion: 回调func fetchVideoData(asset: PHAsset, completion: @escaping (Data?) -> Void) {let options = PHVideoRequestOptions()options.isNetworkAccessAllowed = truePHImageManager.default().requestAVAsset(forVideo: asset, options: options) { avAsset, audioMix, info inif let urlAsset = avAsset as? AVURLAsset {do {let data = try Data(contentsOf: urlAsset.url)completion(data)} catch {completion(nil)}} else {completion(nil)}}}

结语

我们从自定义个媒体选择器入手,来探讨一些Photos框架的用法,本篇博客我们主要介绍了使用Photos获取相册权限,读取媒体数据,以及如何配置读取的数据参数。

下一篇博客我们将开始使用这些数据来构建一个媒体资源选择器的UI页面。

相关文章:

Photos框架 - 自定义媒体资源选择器(数据部分)

引言 在iOS开发中&#xff0c;系统已经为我们提供了多种便捷的媒体资源选择方式&#xff0c;如UIImagePickerController和PHPickerViewController。这些方式不仅使用方便、界面友好&#xff0c;而且我们完全不需要担心性能和稳定性问题&#xff0c;因为它们是由系统提供的&…...

Spring Boot + Spring Cloud 入门

运行配置 java -jar spring-boot-config-0.0.1-SNAPSHOT.jar --spring.profiles.activetest --my1.age32 --debugtrue "D:\Program Files\Redis\redis-server.exe" D:\Program Files\Redis\redis.windows.conf "D:\Program Files\Redis\redis-cli.exe" &q…...

怎么使用动态IP地址上网

如何设置动态IP地址上网&#xff1f; 设置动态IP地址上网的步骤如下&#xff1a; 一、了解动态IP地址 动态IP地址是由网络服务提供商&#xff08;ISP&#xff09;动态分配给用户的IP地址&#xff0c;它会根据用户的需求和网络情况实时改变。相比于静态IP地址&#xff0c;动态…...

【源码+文档+调试讲解】智慧物流小程序的设计与实现

摘 要 互联网发展至今&#xff0c;无论是其理论还是技术都已经成熟&#xff0c;而且它广泛参与在社会中的方方面面。它让信息都可以通过网络传播&#xff0c;搭配信息管理工具可以很好地为人们提供服务。针对高校教师成果信息管理混乱&#xff0c;出错率高&#xff0c;信息安全…...

QT:控件圆角设置、固定窗口大小

实现控件圆角度设置//使用的是setStyleSheet方法 //改变的控件是QTextEdit&#xff0c;如果你想改变其他控件&#xff0c;将QTextEdit进行更换 this->setStyleSheet("QTextEdit{background-color:#FFFFFF;border-top-left-radius:15px;border-top-right-radius:15px;bo…...

【JavaScript】深入理解 `let`、`var` 和 `const`

文章目录 一、var 的声明与特点二、let 的声明与特点三、const 的声明与特点四、let、var 和 const 的对比五、实战示例六、最佳实践 在 JavaScript 中&#xff0c;变量声明是编程的基础&#xff0c;而 let、var 和 const 是三种常用的变量声明方式。本文将详细介绍这三种变量声…...

云监控(华为) | 实训学习day7(10)

水一篇。。。。。。。。。。。。。 强迫症打卡必须要满 企拓 今天没有将东西 2024/7/22 规划学习路线对于进入AI行业至关重要。以下是一个详细的学习路线规划&#xff0c;旨在帮助你从零基础到成为一名合格的AI或大数据分析师&#xff1a; 第一阶段&#xff1a;基础知识建设…...

JS_plus.key.addEventListener监听键盘按键

官方文档&#xff1a;https://www.html5plus.org/doc/zh_cn/key.html 监听事件 plus.key.addEventListener(keydown, e > {console.log("keydown: "e.keyCode) }) plus.key.addEventListener(keyup, e > {console.log("keyup: "e.keyCode) })移除事…...

对话系统(Chat)与自主代理(Agent)对撞

随着生成式AI技术的不断进步&#xff0c;关于其未来发展方向的讨论也愈发激烈。究竟生成式AI的未来是在对话系统&#xff08;Chat&#xff09;中展现智慧&#xff0c;还是在自主代理&#xff08;Agent&#xff09;中体现能力&#xff1f;这一问题引发了广泛的讨论和探索。 首先…...

sql server 连接报错error 40

做个简单的记录,造成40 的原因有很多,你的错误并不一定就是我遇到的这种情况. 错误描述: 首先我在使用ssms 工具连接的时候是可以正常连接的,也能对数据库进行操作. 在使用 ef core 连接 Sql Server 时报错: Microsoft.Data.SqlClient.SqlException (0x80131904): A network-r…...

邮件安全篇:如何防止邮件泄密?

本文主要讨论组织内部用户违反保密规定通过邮件泄密的场景。其他场景导致邮箱泄密的问题&#xff08;如账号被盗、邮件系统存在安全漏洞等&#xff09;不在本文的讨论范围。本文主要从邮件系架构设计、邮件数据防泄漏系统、建立健全规章制度、安全意识培训等方面分别探讨。 1. …...

MySQL查询优化:提升数据库性能的策略

在数据库管理和应用中&#xff0c;优化查询是提高MySQL数据库性能的关键环节。随着数据量的不断增长&#xff0c;如何高效地检索和处理数据成为了一个重要的挑战。本文将介绍一系列优化MySQL查询的策略&#xff0c;帮助开发者和管理员提升数据库的性能。 案例1: 使用索引优化查…...

vue-快速入门

Vue 前端体系、前后端分离 1、概述 1.1、简介 Vue (发音为 /vjuː/&#xff0c;类似 view) 是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建&#xff0c;并提供了一套声明式的、组件化的编程模型&#xff0c;可以高效地开发用户界面。…...

【网络流】——初识(最大流)

网络流-最大流 基础信息引入一些概念基本性质 最大流定义 Ford–Fulkerson 增广Edmons−Karp算法Dinic 算法参考文献 基础信息 引入 假定现在有一个无限放水的自来水厂和一个无限收水的小区&#xff0c;他们之间有多条水管和一些节点构成。 每一条水管有三个属性&#xff1a…...

【STM32嵌入式系统设计与开发---拓展】——1_10矩阵按键

这里写目录标题 1、矩阵按键2、代码片段分析 1、矩阵按键 通过将4x4矩阵按键的每一行依次设为低电平&#xff0c;同时保持其它行为高电平&#xff0c;然后读取所有列的电平状态&#xff0c;可以检测到哪个按键被按下。如果某列变为低电平&#xff0c;说明对应行和列的按键被按下…...

长期更新方法库推荐pmq-ui

# pmq-ui pmq-ui 好用方法库ui库, 欢迎您的使用 ## 安装 1. 克隆项目库到本地&#xff1a; 2. 进入项目目录&#xff1a;cd pmq-ui 3. 安装依赖&#xff1a;npm install pmq-ui ## 使用 <!-- 1. 启动应用&#xff1a; 2. 访问 [http://localhost:3000](http://localhost:300…...

<数据集>抽烟识别数据集<目标检测>

数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;4860张 标注数量(xml文件个数)&#xff1a;4860 标注数量(txt文件个数)&#xff1a;4860 标注类别数&#xff1a;1 标注类别名称&#xff1a;[smoking] 使用标注工具&#xff1a;labelImg 标注规则&#xff1a;对…...

SQL Server 端口设置教程

引言 你好&#xff0c;我是悦创。 在配置 SQL Server 的过程中&#xff0c;设置正确的端口非常关键&#xff0c;因为它影响到客户端如何连接到 SQL Server 实例。默认情况下&#xff0c;SQL Server 使用 TCP 端口 1433&#xff0c;但在多实例服务器上或出于安全考虑&#xff…...

【React1】React概述、基本使用、脚手架、JSX、组件

文章目录 1. React基础1.1 React 概述1.1.1 什么是React1.1.2 React 的特点声明式基于组件学习一次,随处使用1.2 React 的基本使用1.2.1 React的安装1.2.2 React的使用1.2.3 React常用方法说明React.createElement()ReactDOM.render()1.3 React 脚手架的使用1.3.1 React 脚手架…...

k8s部署kafka集群

k8s部署kafka集群 kafka&#xff08;Kafka with KRaft&#xff09; mkdir -p ~/kafka-ymlkubectl create ns kafkacat > ~/kafka-yml/kafka.yml << EOF apiVersion: v1 kind: Service metadata:name: kafka-headlessnamespace: kafkalabels:app: kafka spec:type: C…...

循环冗余码校验CRC码 算法步骤+详细实例计算

通信过程&#xff1a;&#xff08;白话解释&#xff09; 我们将原始待发送的消息称为 M M M&#xff0c;依据发送接收消息双方约定的生成多项式 G ( x ) G(x) G(x)&#xff08;意思就是 G &#xff08; x ) G&#xff08;x) G&#xff08;x) 是已知的&#xff09;&#xff0…...

UE5 学习系列(三)创建和移动物体

这篇博客是该系列的第三篇&#xff0c;是在之前两篇博客的基础上展开&#xff0c;主要介绍如何在操作界面中创建和拖动物体&#xff0c;这篇博客跟随的视频链接如下&#xff1a; B 站视频&#xff1a;s03-创建和移动物体 如果你不打算开之前的博客并且对UE5 比较熟的话按照以…...

关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案

问题描述&#xff1a;iview使用table 中type: "index",分页之后 &#xff0c;索引还是从1开始&#xff0c;试过绑定后台返回数据的id, 这种方法可行&#xff0c;就是后台返回数据的每个页面id都不完全是按照从1开始的升序&#xff0c;因此百度了下&#xff0c;找到了…...

使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装

以下是基于 vant-ui&#xff08;适配 Vue2 版本 &#xff09;实现截图中照片上传预览、删除功能&#xff0c;并封装成可复用组件的完整代码&#xff0c;包含样式和逻辑实现&#xff0c;可直接在 Vue2 项目中使用&#xff1a; 1. 封装的图片上传组件 ImageUploader.vue <te…...

【学习笔记】深入理解Java虚拟机学习笔记——第4章 虚拟机性能监控,故障处理工具

第2章 虚拟机性能监控&#xff0c;故障处理工具 4.1 概述 略 4.2 基础故障处理工具 4.2.1 jps:虚拟机进程状况工具 命令&#xff1a;jps [options] [hostid] 功能&#xff1a;本地虚拟机进程显示进程ID&#xff08;与ps相同&#xff09;&#xff0c;可同时显示主类&#x…...

重启Eureka集群中的节点,对已经注册的服务有什么影响

先看答案&#xff0c;如果正确地操作&#xff0c;重启Eureka集群中的节点&#xff0c;对已经注册的服务影响非常小&#xff0c;甚至可以做到无感知。 但如果操作不当&#xff0c;可能会引发短暂的服务发现问题。 下面我们从Eureka的核心工作原理来详细分析这个问题。 Eureka的…...

以光量子为例,详解量子获取方式

光量子技术获取量子比特可在室温下进行。该方式有望通过与名为硅光子学&#xff08;silicon photonics&#xff09;的光波导&#xff08;optical waveguide&#xff09;芯片制造技术和光纤等光通信技术相结合来实现量子计算机。量子力学中&#xff0c;光既是波又是粒子。光子本…...

Xen Server服务器释放磁盘空间

disk.sh #!/bin/bashcd /run/sr-mount/e54f0646-ae11-0457-b64f-eba4673b824c # 全部虚拟机物理磁盘文件存储 a$(ls -l | awk {print $NF} | cut -d. -f1) # 使用中的虚拟机物理磁盘文件 b$(xe vm-disk-list --multiple | grep uuid | awk {print $NF})printf "%s\n"…...

【C++】纯虚函数类外可以写实现吗?

1. 答案 先说答案&#xff0c;可以。 2.代码测试 .h头文件 #include <iostream> #include <string>// 抽象基类 class AbstractBase { public:AbstractBase() default;virtual ~AbstractBase() default; // 默认析构函数public:virtual int PureVirtualFunct…...

SQL Server 触发器调用存储过程实现发送 HTTP 请求

文章目录 需求分析解决第 1 步:前置条件,启用 OLE 自动化方式 1:使用 SQL 实现启用 OLE 自动化方式 2:Sql Server 2005启动OLE自动化方式 3:Sql Server 2008启动OLE自动化第 2 步:创建存储过程第 3 步:创建触发器扩展 - 如何调试?第 1 步:登录 SQL Server 2008第 2 步…...