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开发中,系统已经为我们提供了多种便捷的媒体资源选择方式,如UIImagePickerController和PHPickerViewController。这些方式不仅使用方便、界面友好,而且我们完全不需要担心性能和稳定性问题,因为它们是由系统提供的&…...

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地址上网? 设置动态IP地址上网的步骤如下: 一、了解动态IP地址 动态IP地址是由网络服务提供商(ISP)动态分配给用户的IP地址,它会根据用户的需求和网络情况实时改变。相比于静态IP地址,动态…...

【源码+文档+调试讲解】智慧物流小程序的设计与实现
摘 要 互联网发展至今,无论是其理论还是技术都已经成熟,而且它广泛参与在社会中的方方面面。它让信息都可以通过网络传播,搭配信息管理工具可以很好地为人们提供服务。针对高校教师成果信息管理混乱,出错率高,信息安全…...

QT:控件圆角设置、固定窗口大小
实现控件圆角度设置//使用的是setStyleSheet方法 //改变的控件是QTextEdit,如果你想改变其他控件,将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 中,变量声明是编程的基础,而 let、var 和 const 是三种常用的变量声明方式。本文将详细介绍这三种变量声…...
云监控(华为) | 实训学习day7(10)
水一篇。。。。。。。。。。。。。 强迫症打卡必须要满 企拓 今天没有将东西 2024/7/22 规划学习路线对于进入AI行业至关重要。以下是一个详细的学习路线规划,旨在帮助你从零基础到成为一名合格的AI或大数据分析师: 第一阶段:基础知识建设…...
JS_plus.key.addEventListener监听键盘按键
官方文档: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技术的不断进步,关于其未来发展方向的讨论也愈发激烈。究竟生成式AI的未来是在对话系统(Chat)中展现智慧,还是在自主代理(Agent)中体现能力?这一问题引发了广泛的讨论和探索。 首先…...

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

邮件安全篇:如何防止邮件泄密?
本文主要讨论组织内部用户违反保密规定通过邮件泄密的场景。其他场景导致邮箱泄密的问题(如账号被盗、邮件系统存在安全漏洞等)不在本文的讨论范围。本文主要从邮件系架构设计、邮件数据防泄漏系统、建立健全规章制度、安全意识培训等方面分别探讨。 1. …...
MySQL查询优化:提升数据库性能的策略
在数据库管理和应用中,优化查询是提高MySQL数据库性能的关键环节。随着数据量的不断增长,如何高效地检索和处理数据成为了一个重要的挑战。本文将介绍一系列优化MySQL查询的策略,帮助开发者和管理员提升数据库的性能。 案例1: 使用索引优化查…...

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

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

【STM32嵌入式系统设计与开发---拓展】——1_10矩阵按键
这里写目录标题 1、矩阵按键2、代码片段分析 1、矩阵按键 通过将4x4矩阵按键的每一行依次设为低电平,同时保持其它行为高电平,然后读取所有列的电平状态,可以检测到哪个按键被按下。如果某列变为低电平,说明对应行和列的按键被按下…...
长期更新方法库推荐pmq-ui
# pmq-ui pmq-ui 好用方法库ui库, 欢迎您的使用 ## 安装 1. 克隆项目库到本地: 2. 进入项目目录:cd pmq-ui 3. 安装依赖:npm install pmq-ui ## 使用 <!-- 1. 启动应用: 2. 访问 [http://localhost:3000](http://localhost:300…...

<数据集>抽烟识别数据集<目标检测>
数据集格式:VOCYOLO格式 图片数量:4860张 标注数量(xml文件个数):4860 标注数量(txt文件个数):4860 标注类别数:1 标注类别名称:[smoking] 使用标注工具:labelImg 标注规则:对…...
SQL Server 端口设置教程
引言 你好,我是悦创。 在配置 SQL Server 的过程中,设置正确的端口非常关键,因为它影响到客户端如何连接到 SQL Server 实例。默认情况下,SQL Server 使用 TCP 端口 1433,但在多实例服务器上或出于安全考虑ÿ…...

【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(Kafka with KRaft) 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…...

铭豹扩展坞 USB转网口 突然无法识别解决方法
当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…...

【机器视觉】单目测距——运动结构恢复
ps:图是随便找的,为了凑个封面 前言 在前面对光流法进行进一步改进,希望将2D光流推广至3D场景流时,发现2D转3D过程中存在尺度歧义问题,需要补全摄像头拍摄图像中缺失的深度信息,否则解空间不收敛…...

JVM虚拟机:内存结构、垃圾回收、性能优化
1、JVM虚拟机的简介 Java 虚拟机(Java Virtual Machine 简称:JVM)是运行所有 Java 程序的抽象计算机,是 Java 语言的运行环境,实现了 Java 程序的跨平台特性。JVM 屏蔽了与具体操作系统平台相关的信息,使得 Java 程序只需生成在 JVM 上运行的目标代码(字节码),就可以…...

Linux部署私有文件管理系统MinIO
最近需要用到一个文件管理服务,但是又不想花钱,所以就想着自己搭建一个,刚好我们用的一个开源框架已经集成了MinIO,所以就选了这个 我这边对文件服务性能要求不是太高,单机版就可以 安装非常简单,几个命令就…...

协议转换利器,profinet转ethercat网关的两大派系,各有千秋
随着工业以太网的发展,其高效、便捷、协议开放、易于冗余等诸多优点,被越来越多的工业现场所采用。西门子SIMATIC S7-1200/1500系列PLC集成有Profinet接口,具有实时性、开放性,使用TCP/IP和IT标准,符合基于工业以太网的…...
用鸿蒙HarmonyOS5实现中国象棋小游戏的过程
下面是一个基于鸿蒙OS (HarmonyOS) 的中国象棋小游戏的实现代码。这个实现使用Java语言和鸿蒙的Ability框架。 1. 项目结构 /src/main/java/com/example/chinesechess/├── MainAbilitySlice.java // 主界面逻辑├── ChessView.java // 游戏视图和逻辑├──…...

归并排序:分治思想的高效排序
目录 基本原理 流程图解 实现方法 递归实现 非递归实现 演示过程 时间复杂度 基本原理 归并排序(Merge Sort)是一种基于分治思想的排序算法,由约翰冯诺伊曼在1945年提出。其核心思想包括: 分割(Divide):将待排序数组递归地分成两个子…...

DAY 45 超大力王爱学Python
来自超大力王的友情提示:在用tensordoard的时候一定一定要用绝对位置,例如:tensorboard --logdir"D:\代码\archive (1)\runs\cifar10_mlp_experiment_2" 不然读取不了数据 知识点回顾: tensorboard的发展历史和原理tens…...
CppCon 2015 学习:REFLECTION TECHNIQUES IN C++
关于 Reflection(反射) 这个概念,总结一下: Reflection(反射)是什么? 反射是对类型的自我检查能力(Introspection) 可以查看类的成员变量、成员函数等信息。反射允许枚…...
Netty自定义协议解析
目录 自定义协议设计 实现消息解码器 实现消息编码器 自定义消息对象 配置ChannelPipeline Netty提供了强大的编解码器抽象基类,这些基类能够帮助开发者快速实现自定义协议的解析。 自定义协议设计 在实现自定义协议解析之前,需要明确协议的具体格式。例如,一个简单的…...