Photos框架 - 自定义媒体选择器(UI预览)

news2024/12/27 17:42:19

554b3494cc0f42debc0c893bac502f04.png

引言

在前面的博客中我们已经介绍了使用媒体资源数据的获取,以及自定义的媒体资源选择列表页。在一个功能完整的媒体选择器中,预览自然是必不可少的,本篇博客我们就来实现一个资源的预览功能,并且实现列表和预览的数据联动效果。

预览功能实现

预览功能包括图片预览和视频预览,并且在预览的同时最好的情况就是我们仍然知道当前正在预览的资源是否已经被选中了,或者说在预览的同时我们仍然可以选择和取消选中。这就需要我们在数据上花点心思。

预览UI

预览页面我们需要创建一个新的视图控制器,然后采用一个全屏的UICollectionView来实现,它的每个元素也都是和屏幕相同大小,具体代码如下:

    /// 添加列表
    func addCollectionView()  {
        let layout = UICollectionViewFlowLayout()
        layout.scrollDirection = .horizontal
        layout.itemSize = CGSize(width: CS_SCREENWIDTH, height: CS_SCREENHIGHT)
        layout.minimumLineSpacing = 0.0
        layout.minimumInteritemSpacing = 0.0
        collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
        collectionView.backgroundColor = .white
        collectionView.delegate = self
        collectionView.dataSource = self
        collectionView.isPagingEnabled = true
        self.view.addSubview(collectionView)
        collectionView.snp.makeConstraints { make in
            make.top.equalToSuperview()
            make.leading.trailing.bottom.equalToSuperview()
        }
        collectionView.register(PHMediaPreViewCell.self, forCellWithReuseIdentifier: NSStringFromClass(PHMediaPreViewCell.self))
    }

预览cell的元素内容几乎和列表相同,需要有选中标签,也需要有视频时长等等,具体代码如下:

class PHMediaPreViewCell: UICollectionViewCell {
    
    /// 图片
    private let imageView = UIImageView()
    /// 选中标签
    private var selectTag = UIButton()
    /// 播放按钮
    private var playButton = UIButton()
    /// 资源模型
    private var mediaModel: PHMediaModel?
    /// 资源管管理类
    var mediaManager:PHMediaManager?
    /// 选中或取消回调
    var selectTagTouchBlock: ((PHMediaModel) -> Void)?
    ....
}

包括它的标签选中逻辑,和画面渲染逻辑也都大同小异,只是布局的样式略有不同,另外它需要加载的图片是原图,而不是缩略图,具体的渲染代码如下:

    /// 渲染数据
    func renderData(mediaModel: PHMediaModel, mediaManager: PHMediaManager) {
        self.mediaModel = mediaModel
        self.mediaManager = mediaManager
        playButton.isHidden = mediaModel.mediaType != .video
        let duration = Int(mediaModel.videoDuration)
        let title = secondsToHourMinuteSecond(seconds: duration)
        playButton.setTitle(title, for: .normal)
        self.mediaManager?.fetchThumbnail(asset: mediaModel.asset!, completion: {[weak self] (image) in
            guard let self = self else { return }
            self.imageView.image = image
        })
    }

预览数据

为了让缩略图列表和预览列表的数据可以联动,在两个页面控制器中我们都是用PHMediaManager来管理显示的媒体数据列表和选中的媒体数据列表。

另外需要定义index来指定当我们从列表页面进入预览页面时的资源索引。

还定义了一个选择的回调,用来给列表页面同步UI。

    /// 资源管理类
    var mediaManager: PHMediaManager!
    /// 当前index
    var currentIndex: Int = 0
    /// 选择回调
    var selectMediaBlock: (() -> Void)?

当进入预览页面,首先同步索引,将预览的图片定位到我们在列表中点击的媒体资源,代码如下:

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        collectionView.scrollToItem(at: IndexPath(row: currentIndex, section: 0), at: .centeredHorizontally, animated: false)
    }

之后通过mediaManager中的displayMediaModels来渲染列表数据:

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return mediaManager.displayMediaModels.count
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: NSStringFromClass(PHMediaPreViewCell.self), for: indexPath) as! PHMediaPreViewCell
        let mediaModel = mediaManager.displayMediaModels[indexPath.row]
        cell.renderData(mediaModel: mediaModel, mediaManager: mediaManager)
        let index = mediaManager.selectedMediaModels.firstIndex(of: mediaModel) ?? -1
        cell.index = index
        cell.selectTagTouchBlock = { [weak self] mediaModel in
            self?.touchMediaModel(mediaModel: mediaModel)
        }
        return cell
    }

关于媒体资源选中和取消选中的回调,和列表中的处理完全一致,只是多了一个block调用通知列表刷新UI:

    /// 媒体资源选中和取消的回调
    private func touchMediaModel(mediaModel:PHMediaModel) {
        if mediaModel.isSelected {
            mediaModel.isSelected = false
            if let index = mediaManager.selectedMediaModels.firstIndex(of: mediaModel) {
                mediaManager.selectedMediaModels.remove(at: index)
            }
        } else {
            if mediaManager.selectedMediaModels.count >= mediaManager.maxSelectedCount {
                print("最多选中\(mediaManager.maxSelectedCount)个")
                return
            }
            mediaModel.isSelected = true
            mediaManager.selectedMediaModels.append(mediaModel)
        }
        collectionView.reloadData()
        selectMediaBlock?()
    }

使用预览

这样我们就需要把原来的列表点击事件做一下调整,点击后让它显示预览画面,代码如下:

    /// 媒体点击
    private func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath,mediaModel:PHMediaModel) {
        self.showPreView(index: indexPath.row - actionArray.count)
    }
    // 显示预览
    private func showPreView(index:Int) {
        let preVC = PHMediaPreViewController()
        preVC.mediaManager = mediaManager
        preVC.currentIndex = index
        preVC.modalPresentationStyle = .fullScreen
        self.present(preVC, animated: false, completion: nil)
        preVC.selectMediaBlock = { [weak self]  in
            self?.collectionView.reloadData()
        }
    }

结语

有了前面媒体列表功能做基础,预览的功能实现起来显得轻松了许多,只不过是将加载缩略图替换为了加载原图。

需要注意的一点,列表和预览必须公用通一个mediaManager这样才能保证数据的一致。

另外本篇博客我们没有涉及到视频资源的预览,因为它将会设计AVKit或者AV Foundation框架中的内容,在我的其它博客专栏中曾经有过介绍,有兴趣的可以查看一下。

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

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

相关文章

内网横向——远程桌面利用

文章目录 一、远程桌面的确定和开启二、RDP Hijacking 网络拓扑: 攻击机kali IP:192.168.111.0 跳板机win7 IP:192.168.111.128,192.168.52.143 靶机win server 2008 IP:192.168.52.138 一、远程桌面的确定和开启 下面…

VMware、Docker - 让虚拟机走主机代理,解决镜像封禁问题

文章目录 虚拟机全局代理配置找到 VMnet8 的 IPv4 地址代理相关配置虚拟机代理配置 Docker 代理配置修改镜像修改 Docker 代理配置 虚拟机全局代理配置 找到 VMnet8 的 IPv4 地址 a)打开此电脑,输入 “控制面板”,然后回车. b)之…

元注解相关知识总结

Target 这个注解适用于决定注解的适用范围,例如适用于构造器,方法,字段等 Retention 这个注解的作用是确定注解的生命周期一般用得比较多的是RunTime这样就可以在运行环境中使用它,赋值的方式一般是使用value进行赋值 SOURCE 代…

【初阶数据结构篇】顺序表的实现(赋源码)

文章目录 本篇代码位置顺序表和链表1.线性表2.顺序表2.1 概念与结构2.2分类2.2.1 静态顺序表2.2.2 动态顺序表 2.3 动态顺序表的实现2.3.1动态顺序表的初始化和销毁及打印2.3.2动态顺序表的插入动态顺序表的尾插动态顺序表的头插动态顺序表的在指定位置插入数据 2.3.3动态顺序表…

spring常用注解有哪些

Spring框架使用了大量的注解来简化配置和开发,以下是一些常用的Spring注解: 1.Component:通用的构造型注解,用于标记一个类作为Spring管理的组件,通常用于自定义组件。 2.Autowired:用于自动装配Bean&#…

Android Add a Header to the GridLayout

RecyclerView 添加 Header, RecycleView 设置 LayoutManager 为 GridLayoutManger的 时候; Header 要占用3行 val manager GridLayoutManager(activity, 3)binding.sleepList.layoutManager manager// span size for each position, and assign it to manager.spa…

Golang零基础入门课_20240726 课程笔记

视频课程 最近发现越来越多的公司在用Golang了,所以精心整理了一套视频教程给大家,这个只是其中的第一部,后续还会有很多。 视频已经录制完成,完整目录截图如下: 课程目录 01 第一个Go程序.mp402 定义变量.mp403 …

github简单地操作

1.调节字体大小 选择options 选择text 选择select 选择你需要的参数就可以了。 2.配置用户名和邮箱 桌面右键,选择git Bash Here git config --global user.name 用户名 git config --global user.email 邮箱名 3.用git实现代码管理的过程 下载别人的项目 git …

PlatformIO+ESP32S3学习:通过WIFI与和风天气API获取指定地点的天气情况并显示

1. 硬件准备 你只需要有一个ESP32S3开发板。我目前使用的是: 购买地址:立创ESP32S3R8N8 开发板 2. 和风天气API 2.1. 和风天气介绍 和⻛天气是中国领先的气象科技服务商、国家高新技术 企业,致力于运用先进气象模型结合大数据、人工智能 技术…

Halcon 引擎方式调试

1.C# 端添加代码 启动调试模式 public HDevEngine MyEngine new HDevEngine(); // halcon引擎;// 启动调试服务 MyEngine.StartDebugServer();2.Halcon程序添加到进程 打开Halcon程序 【执行】>【附加到进程】 点击【确定】 3.C# 程序执行到相关位置 C# 程序执行调用…

大模型学习笔记十四:Agent模型微调

文章目录 一、大模型需要Agent技术的原因二、Prompt Engineering可以实现Agent吗?(1)ReAct原理展示和代码(2)ModelScope(3)AutoGPT(4)ToolLLaMA 三、既然AutoGPT可以满足…

Java SE 文件上传和文件下载的底层原理

1. Java SE 文件上传和文件下载的底层原理 文章目录 1. Java SE 文件上传和文件下载的底层原理2. 文件上传2.1 文件上传应用实例2.2 文件上传注意事项和细节 3. 文件下载3.1 文件下载应用实例3.2 文件下载注意事项和细节 4. 总结:5. 最后: 2. 文件上传 文件的上传和…

【C语言】指针基础知识理解

1. 内存和地址 1.1 内存 在讲内存和地址之前,我们想有个⽣活中的案例: 假设你在学校宿舍楼,但是宿舍的房间没有编号,你的⼀个朋友发微信来找你玩,如果想找到你,就得挨个房⼦去找,这样效率会很…

《算法笔记》总结No.11——数字处理(上)欧拉筛选

机试中存在部分涉及到较复杂数字的问题,这是编码的基本功,各位一定要得心应手。 目录 一.最大公约数和最小公倍数 1.最大公约数 2.最小公倍数 二.素数 1.判断指定数 2.输出所有素数 3.精进不休——埃拉托斯特尼筛法 4.达到更优!——…

STL-string(使用和部分模拟实现)

1.string basic_string<char> 是 C 标准库中定义的一个模板类型,用于表示一个字符串。这个模板类接收一个字符类型作为模板参数。typedef basic_string<char> string&#xff1a;string类是basic_string类模板的实例化&#xff0c;它使用 char作为其字符类型。 2.…

【北京迅为】《i.MX8MM嵌入式Linux开发指南》-第三篇 嵌入式Linux驱动开发篇-第六十一章 Linux内核定时器

i.MX8MM处理器采用了先进的14LPCFinFET工艺&#xff0c;提供更快的速度和更高的电源效率;四核Cortex-A53&#xff0c;单核Cortex-M4&#xff0c;多达五个内核 &#xff0c;主频高达1.8GHz&#xff0c;2G DDR4内存、8G EMMC存储。千兆工业级以太网、MIPI-DSI、USB HOST、WIFI/BT…

科普文:分布式数据一致性协议Paxos

1 什么是Paxos Paxos协议其实说的就是Paxos算法, Paxos算法是基于消息传递且具有高度容错特性的一致性算 法&#xff0c;是目前公认的解决分布式一致性问题最有效的算法之一。 Paxos由 莱斯利兰伯特(Leslie Lamport)于1998年在《The Part-Time Parliament》论文中首次公 开&…

KDP开源平台升级,推进大数据处理迈向轻量化、智能化

本文由 LeetTools 工具生成 编辑 | June 在当今数字化转型的浪潮中&#xff0c;企业面临着如何高效管理和利用大数据的挑战。智领云推出的Kubernetes Data Platform&#xff08;简称KDP&#xff09;正是为了解决这一问题而设计的。作为一款开源的云原生大数据平台&#xff0c;K…

【前端 08】简单学习js字符串

JavaScript中的String对象详解 在JavaScript中&#xff0c;字符串&#xff08;String&#xff09;是一种非常基础且常用的数据类型&#xff0c;用于表示文本数据。虽然JavaScript中的字符串是原始数据类型&#xff0c;但它们的行为类似于对象&#xff0c;因为JavaScript为字符…

[C#]调用本地摄像头录制视频并保存

AForge.NET是一个基于C#框架设计的开源计算机视觉和人工智能库&#xff0c;专为开发者和研究者设计。它提供了丰富的图像处理和视频处理算法、机器学习和神经网络模型&#xff0c;具有高效、易用、稳定等特点。AForge库由多个组件模块组成&#xff0c;包括AForge.Imaging&#…