UIKit 在 UICollectionView 中拖放交换 Cell 视图的极简实现

在这里插入图片描述

概览

UIKit 中的 UICollectionView 视图是我们显示多列集合数据的不二选择,而丰富多彩的交互操作更是我们选择 UICollectionView 视图的另一个重要原因。

在这里插入图片描述

如上图所示:我们实现了在 UICollectionView 中拖放交换任意两个 Cell 子视图的功能,这是怎么做到的呢?

在本篇博文中,您将学到如下内容:

  • 概览
  • 1. UICollectionView 拖放交互基本思路
  • 2. 构建 storyboard
  • 3. 准备数据源
  • 4. 遵守拖放代理
  • 5. 更快的响应交换操作
  • 总结

其实,完成这样一种交换远比小伙伴们想象的要简单的多!

所以,还等什么呢?Let‘s find out!!!😉


1. UICollectionView 拖放交互基本思路

在 iOS(iPadOS/MacOS) 中,广义的拖放操作涉及到跨越不同 App 间的范畴:比如,我们常常希望将微信中的图片直接拖动到 QQ 的聊天界面中去。

不过,这里我们只想在 App 内部进行拖动,所以完成起来就要简单许多。我们的数据元素不需要满足 NSItemProvider 或 NSSecureCoding 等一些苛刻限制,只是单纯的数据即可。

一般来说,为了完成拖放,我们需要在对应视图上实现 UIDragInteractionDelegate 和 UIDropInteractionDelegate 协议指定的方法。

而对于 UICollectionView 视图,其子 Cell 间的拖动我们还有更简单的方式:遵守 UICollectionViewDragDelegate 和 UICollectionViewDropDelegate 协议即可。

在这里插入图片描述

iOS(iPadOS/MacOS) 系统中关于拖放(Drag & Drop)更详细全面的介绍,苹果官网无疑是一个很好的选择:

  •  Drag and drop

2. 构建 storyboard

首先,新建一个 UIKit 项目,打开 Main 故事板(Main.storyboard)为视图控制器添加一个 UICollectionView 子视图:

在这里插入图片描述

如上图所示,我们随后又在 UICollectionView 中添加了一个静态的 UICollectionViewCell 控件:

在这里插入图片描述

然后,调整 UICollectionViewCell 背景以及 Label 字体大小和颜色到满意为止:

在这里插入图片描述

最后,妥善设置好 UICollectionViewCell 的 ID:

在这里插入图片描述

并将 UICollectionView 关联到视图控制器的 collectionView 属性上:

在这里插入图片描述

3. 准备数据源

现在界面布局已准备就绪,我们接下来需要创建数据模型:

struct Item {var id = UUID()var title: Stringstatic var preview: [Item] {[Item(title: "Apple"), Item(title: "Banana"),Item(title: "Cherry"), Item(title: "Date"),Item(title: "Dragon"), Item(title: "Sheep"),Item(title: "V-Malicious"), Item(title: "X-Code"),Item(title: "GreatWall"), Item(title: "TaiTan"),Item(title: "Milk"), Item(title: "🥸"),]}
}

是滴,我们只需要一个简单的结构类型就可以了!

然后,让我们的 ViewController 遵守 UICollectionViewDataSource 协议,并实现相关方法:

class ViewController: UIViewController, UICollectionViewDataSource {@IBOutlet weak var collectionView: UICollectionView!var items = Item.previewfunc collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {items.count}func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! Cellcell.layer.cornerRadius = 15.0cell.label.text = items[indexPath.row].titlereturn cell}override func viewDidLoad() {super.viewDidLoad()collectionView.dataSource = self}
}

注意,我们并没有让 ViewController 遵守 UICollectionViewDelegate 协议,因为它和这里的拖动操作基本上没有半毛线关系。

4. 遵守拖放代理

上面说过,为了让 UICollectionView 中的 Cell 子视图支持拖放,我们需要先让视图控制器遵守 UICollectionViewDragDelegate 和 UICollectionViewDropDelegate 协议:

extension ViewController: UICollectionViewDragDelegate {func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {// 保存源 Item 索引srcIndex = indexPathlet itemProvider = NSItemProvider(object: "Item" as NSString)return [UIDragItem(itemProvider: itemProvider)]}
}extension ViewController: UICollectionViewDropDelegate {func collectionView(_ collectionView: UICollectionView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UICollectionViewDropProposal {// 保存目的 Item 索引self.destIndex = destinationIndexPathreturn .init(operation: .move)}func collectionView(_ collectionView: UICollectionView, dropSessionDidEnd session: UIDropSession) {        guard let srcIndex, let destIndex else { return }swapItems(from: srcIndex, to: destIndex)self.srcIndex = nilself.destIndex = nil}
}

对于上面的代码,需要说明的是:

  • collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) 在拖动开始时被调用,我们可以趁机保存源 Item 的信息;
  • collectionView(_ collectionView: UICollectionView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) 在拖动中被多次调用,其中传入的 destinationIndexPath 参数表示目的 Cell 的索引(如果下方有的话),我们可以借此保存目的 Item 的信息;
  • collectionView(_ collectionView: UICollectionView, dropSessionDidEnd session: UIDropSession) 在结束放置时被调用,我们此时可以实现交换对应 Cell 的操作;

随后,我们还需要在 ViewController 中添加所需的属性和方法:

var srcIndex: IndexPath?
var destIndex: IndexPath?private func swapItems(from srcIndex: IndexPath, to destIndex: IndexPath) {items.swapAt(srcIndex.row, destIndex.row)// 将源和目的 Cell 的移动放到批处理中以产生流畅的动画:collectionView.performBatchUpdates {collectionView.moveItem(at: srcIndex, to: destIndex)collectionView.moveItem(at: destIndex, to: srcIndex)}
}

最后,在视图控制器加载时为其绑定对应的拖放代理,并开启拖动交互:

override func viewDidLoad() {super.viewDidLoad()collectionView.dataSource = selfcollectionView.dropDelegate = selfcollectionView.dragDelegate = selfcollectionView.dragInteractionEnabled = true
}

现在,运行 App 看一下效果:

在这里插入图片描述

不过,小伙伴们或许发现了,拖动放置后 Cell 交换会有略微延时,这是怎么回事呢?

5. 更快的响应交换操作

上面 Cell 拖动交换会慢一拍的原因为:我们是在 collectionView(_ collectionView: UICollectionView, dropSessionDidEnd session: UIDropSession) 方法中执行 swapItems() 来完成交换的。

而系统对该方法的回调触发是比较“谨慎”的,它会在判断用户彻底抬起手指后才能得到运行机会。

如果希望用户在目标 Cell 上抬起手指时能够立即完成交换行为,我们可以将 swapItems() 方法放到 UICollectionViewDragDelegate 协议中的 collectionView(_ collectionView: UICollectionView, dragSessionDidEnd session: UIDragSession) 的回调中去执行,因为该方法识别触发的速度要快得多:

extension ViewController: UICollectionViewDragDelegate {func collectionView(_ collectionView: UICollectionView, dragSessionDidEnd session: UIDragSession) {guard let srcIndex, let destIndex else { return }swapItems(from: srcIndex, to: destIndex)self.srcIndex = nilself.destIndex = nil}
}

再次运行 App 看一下:

在这里插入图片描述

现在,我们 UICollectionView 中的拖放交换操作的速度又上了一个新台阶,还不快给自己一个大大的赞吗?棒棒哒!💯

总结

在本篇博文中,我们讨论了 UIKit 中 UICollectionView 视图拖放操作的基本原理,并用最简单的代码实现了 UICollectionView 视图中 Cell 的交换功能。

感谢观赏,再会!😎

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

不要抱怨,不如抱 Java 运算符吧 (1)

本篇会加入个人的所谓‘鱼式疯言’ ❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言 而是理解过并总结出来通俗易懂的大白话, 小编会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的. 🤭🤭🤭可能说的不是那么严谨.但小编初心是能让更多人…

学习鸿蒙基础(5)

一、honmonyos的page路由界面的路径 新建了一个page,然后删除了。运行模拟器的时候报错了。提示找不到这个界面。原来是在路由界面没有删除这个page。新手刚接触找了半天才找到这个路由。在resources/base/profile/main_pages.json 这个和微信小程序好类似呀。 吐槽&#xf…

普中51单片机学习(串口通信)

串口通信 原理 计算机通信是将计算机技术和通信技术的相结合,完成计算机与外部设备或计算机与计算机之间的信息交换 。可以分为两大类:并行通信与串行通信。并行通信通常是将数据字节的各位用多条数据线同时进行传送 。控制简单、传输速度快&#xff1…

百度智能云分布式数据库 GaiaDB-X 与龙芯平台完成兼容认证

近日,百度智能云的分布式关系型数据库软件 V3.0 与龙芯中科技术股份有限公司的龙芯 3C5000L/3C5000 处理器平台完成兼容性测试,功能与稳定性良好,获得了龙架构兼容互认证证书。 龙芯系列处理器 通用 CPU 处理器是信息产业的基础部件&#xf…

LINUX读取RTC实时时钟时间

linux 读写RTC时间_linux rtc 读写-CSDN博客

《游戏引擎架构》--学习3

内存管理 优化动态内存分配 维持最低限度的堆分配,并且永不在紧凑循环中使用堆分配 容器 迭代器 Unicode

LeetCode 0590. N 叉树的后序遍历:深度优先搜索(DFS)

【LetMeFly】590.N 叉树的后序遍历:深度优先搜索(DFS) 力扣题目链接:https://leetcode.cn/problems/n-ary-tree-postorder-traversal/ 给定一个 n 叉树的根节点 root ,返回 其节点值的 后序遍历 。 n 叉树 在输入中按层序遍历进行序列化表…

【Linux】自主WEB服务器实现

自主web服务器实现 1️⃣构建TcpServer2️⃣构建HttpServer3️⃣构建HttpRequest和HttpResponseHttp请求报文格式Http相应报文读取、处理请求&构建响应读取请求中的一行读取请求中需要注意的点 4️⃣CGI模式判断是否需要用CGI处理请求构建任务&线程池管理 5️⃣实验结果…

解决pycharm中PIL安装失败

问题:在调用pil时显示pil标红 我在设置中下载每次失败,显示 ERROR: Could not find a version that satisfies the requirement PIL (from versions: none) ERROR: No matching distribution found for PIL我尝试了很久,查看了一些博客 &a…

odoo16-API(Controller)带有验证访问的接口

odoo16-API(Controller)带有验证访问的接口 目前我使用odoo原生的登录token来验证登陆的有效性 废话不多说直接上代码 # 测试获取session_id import requests class GetOdooData(http.Controller):def getOdooToken(self):# http://localhost:8123访问…

OpenAI 的 GPTs 提示词泄露攻击与防护实战:防御卷(一)

前面的OpenAI DevDay活动上,GPTs技术的亮相引起了广泛关注。随着GPTs的创建权限开放给Plus用户,社区里迅速涌现了各种有趣的GPT应用,这些都是利用了Prompt提示词的灵活性。这不仅展示了技术的创新潜力,也让人们开始思考如何获取他…

AI新工具(20240222)SDXL-Lightning-节跳动开发一个快速的文本到图像生成模型;GoEnhance - 视频风格转换等

SDXL-Lightning - 节跳动开发一个快速的文本到图像生成模型 SDXL-Lightning是字节跳动开发一个快速的文本到图像生成模型,能够在几个步骤内生成高质量的1024像素图像。该模型发布用于研究目的,可以从stabilityai/stable-diffusion-xl-base-1.0中提取模型…

nginx之web性能location优先级

4.2 event事件 events {worker_connections 65536; #设置单个工作进程的最大并发连接数use epoll;#使用epoll事件驱动,Nginx支持众多的事件驱动,比如:select、poll、epoll,只能设置在events模块中设置。accept_mutex on; #on为同一时刻一个…

【OpenFeign常用配置】

OpenFeign常用配置 快速入门:1、引入依赖2、启用OpenFeign 实践1、引入依赖2、开启连接池功能3、模块划分4、日志5、重试 快速入门: OpenFeign是一个声明式的http客户端,是spring cloud在eureka公司开源的feign基础上改造而来。其作用及时基于…

医药之链:基于Django的智能药品管理系统

框架 Python 3.7 django 3.2.13 Bootstrap(前端) sqlite(数据库)导包 pip install django3.2.13 pip install pandas pip install xlwt环境搭建 登录 zfx 123456

Vue样式绑定

1. 绑定 HTML class ①通过class名称的bool值判断样式是否被启用 <template><!--通过样式名称是否显示控制样式--><div :class"{ haveBorder: p.isBorder, haveBackground-color: p.isBackgroundcolor }">此处是样式展示区域</div><br /…

Kotlin学习 6

1.接口 interface Movable {var maxSpeed: Intvar wheels: Intfun move(movable: Movable): String}class Car(var name: String, override var wheels: Int 4, _maxSpeed: Int) : Movable {override var maxSpeed: Int _maxSpeedget() fieldset(value) {field value}overr…

typescript 索引签名类型

ts索引类型简介 在TypeScript中&#xff0c;索引签名类型&#xff08;Index Signature Type&#xff09;是一种特殊的类型&#xff0c;它定义了对象中键的类型以及相应的值的类型。通过使用索引签名类型&#xff0c;我们可以表示一个对象&#xff0c;该对象的键可以是任意类型…

pytest 框架自动化测试

随笔记录 目录 1. 安装 2. 安装pytest 相关插件 2.1 准备阶段 2.2 安装 2.3 验证安装成功 3. pytest测试用例的运行方式 3.1 主函数模式 3.1.1 主函数执行指定文件 3.1.2 主函数执行指定模块 3.1.3 主函数执行某个文件中的某个类、方法、函数 3.1.4 主函数执行生…

Vue3之ref与reactive的基本使用

ref可以创建基本类型、对象类型的响应式数据 reactive只可以创建对象类型的响应式数据 接下来让我为大家介绍一下吧&#xff01; 在Vue3中&#xff0c;我们想让数据变成响应式数据&#xff0c;我们需要借助到ref与reactive 先为大家介绍一下ref如何使用还有什么注意点 我们需…