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.mzph.cn/news/696806.shtml

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

相关文章

Zabbix 远程监控主机

目录 1、安装 Zabbix 安装客户端 服务端测试通讯 Web页面添加主机 2、监控 Nginx 自定义脚本监控 Nginx web配置台 3、监控 MySQL 配置模版文件 配置Web界面 1、安装 Zabbix node-12 作为zabbix的被监控端,提供mysql服务器,配置zabbix监控node…

jquery写组件滑动人机验证组件

jquery组件,虽然 jquery 语法古老,但是写好了用起来真的很爽啊,本文用滑动人机验证给大家做个详细教程(直接复制代码就可以用噢o(* ̄▽ ̄*)ブ) 第一步 先看下组件本身 component.js (function() {…

Nginx网络服务三-----(三方模块和内置变量)

1.验证模块 需要输入用户名和密码 我们要用htpasswd这个命令,先安装一下httpd 生成文件和用户 修改文件 访问页面 为什么找不到页面? 对应的路径下,没有这个文件 去创建文件 去虚拟机浏览器查看 有的页面不想被别人看到,可以做…

【UI自动化】使用poco框架进行元素唯一定位

直接选择: 1.poco(text买入).click() 2.poco("android.widget.ImageView").click()相对选择、空间选择: 3.poco(text/name).parent().child()[0].click()正则表达式: 4.listpoco(textMatches".*ETF")今天主要想记录下…

centos 系统盘 放到 win pc 中的异常解决

有一块 2.5 480g sata ssd,之前是笔记本电脑的centos系统盘,后来没用了,打算挂到台式机上当下载盘。台式机pc的主板是华硕 h610m-a。 难点一: 因为台式pc上已经挂了两块3.5 hdd,发现sata的电源线都在3.5hdd附近&#…

利用RBI(Remote Browser Isolation)技术访问ChatGPT

系统组网图 #mermaid-svg-Bza2puvd8MudMbqR {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-Bza2puvd8MudMbqR .error-icon{fill:#552222;}#mermaid-svg-Bza2puvd8MudMbqR .error-text{fill:#552222;stroke:#552222;…

300分钟吃透分布式缓存-10讲:MC是怎么定位key的?

我们在进行 Mc 架构剖析时,除了学习 Mc 的系统架构、网络模型、状态机外,还对 Mc 的 slab 分配、Hashtable、LRU 有了简单的了解。本节课,将进一步深入学习这些知识点。 接下来,进入 Memcached 进阶的学习。会讲解 Mc 是如何进行…

QT应用软件【协议篇】周立功CAN接口卡代码示例

文章目录 USBCAN系列CAN接口卡规格参数资料下载QT引用周立功的库安装sdk代码USBCAN系列CAN接口卡 USBCAN系列CAN接口卡兼容USB2.0全速规范,可支持1/2/4/8路CAN接口。采用该接口卡,PC机可通过USB连入CAN网络,进行CAN总线数据采集和处理,主要具备以下几大优势特点: 支持车载…

正交匹配追踪(Orthogonal Matching Pursuit, OMP)的MATLAB实现

压缩感知(Compressed Sensing, CS)是一种利用稀疏信号的先验知识,用远少于奈奎斯特采样定理要求的样本数目恢复整个信号的技术。正交匹配追踪(Orthogonal Matching Pursuit, OMP)是一种常见的贪婪算法(Gree…

在苹果电脑MAC上安装Windows10(双系统安装的详细图文步骤教程)

在苹果电脑MAC上安装Windows10(双系统安装的详细图文步骤教程) 一、准备工作准备项1:U盘作为系统安装盘准备项2:您需要安装的系统镜像 二、启动转换助理步骤1:找到启动转换助理步骤2:启动转换助理步骤3&…

波奇学Linux:进程通信管道

进程通信 管道:基于文件级别的单向通信 创建父子进程,使得进程的struct file*fd_array[]的文件描述符指向同一个struct file文件,这个文件是内存级文件。 父进程关写端,子进程再关闭读端。实现单向通信 子进程写入,父进…

C++ Primer 笔记(总结,摘要,概括)——第3章 字符串、向量和数组

目录 3.1 命名空间的using声明 3.2 标准库类型string 3.2.1 定义和初始化string对象 3.2.2 string对象上的操作 3.2.3 处理string对象中的字符 3.3 标准库类型vector 3.3.1 定义和初始化vector对象 3.3.2 向vector对象中添加元素 3.3.3 其他vector操作 3.4 迭代器介绍 3.4.…

如何使用rocketmq实现分布式事务?

什么是rocketmq事务消息 事务消息是 Apache RocketMQ 提供的一种高级消息类型,支持在分布式场景下保障消息生产和本地事务的最终一致性。 RocketMQ的分布式事务又称为“半消息事务”。 事务消息处理流程 RocketMQ是靠半消息机制实现分布式事务 事务消息&#x…

Spring之AOP源码解析(上)

Aop相关注解 EnableTransactionManagementEnableAspectJAutoProxyEnableAsync... 从注解切入来看看这些注解都干了什么 Import注解作用简述 注入的类一般继承ImportSelector或者ImportBeanDefinitionRegistrar接口 继承ImportSelector接口:selectImports方法返回…

pandas/geopandas 笔记:判断地点在不在路网上 不在路网的点和路网的距离

0 导入库 import osimport pandas as pd pd.set_option(display.max_rows,5)import osmnx as oximport geopandas as gpd from shapely.geometry import Point 1 读取数据 假设我们有 如下的数据: 1.1 新加坡室外基站位置数据 cell_stationpd.read_csv(outdoor…

TSINGSEE青犀AI智能分析网关V4初始配置与算法相关配置介绍

TSINGSEE青犀AI智能分析网关V4内置了近40种AI算法模型,支持对接入的视频图像进行人、车、物、行为等实时检测分析,上报识别结果,并能进行语音告警播放。硬件管理平台支持RTSP、GB28181协议、以及厂家私有协议接入,可兼容市面上常见…

linux下ffmpeg调用GPU硬件解码(VDPAU/VAAPI)保存文件

本文讲解在linux下面,如何通过ffmpeg调用GPU硬件解码,并保存解码完的yuv文件。 其实,ffmpeg自带的例子hw_decode.c这个文件,就已经能满足要求了,因此,本文就尝试讲解以下hw_decode这个例子。hw_decode.c可以…

watchpoint

前言 内存被踩,通过 watchpoint 找到真凶 实例 以 smsc911x 网卡驱动为基体,进行实验,和网卡本身功能无关, 每执行一次 ifconfig eth0 up,就会调用一次 smsc911x_open(),我在这里设计了一段代码&#xf…

数学知识(四)(容斥原理、博弈论)

一、容斥原理 容斥原理公式 一共加或者减的式子个数 (一)利用容斥原理解决求能被质数整除的数的个数 890计算能被整除的数的个数 因为一共有2^n-1种选法,可以用位运算的方式枚举,对于得到的每一种选法,根据存在的数…

六、回归与聚类算法 - 逻辑回归与二分类

线性回归欠拟合与过拟合线性回归的改进 - 岭回归分类算法:逻辑回归模型保存与加载无监督学习:K-means算法 1、应用场景 2、原理 2.1 输入 2.2 激活函数 3、损失以及优化 3.1 损失 3.2 优化 4、逻辑回归API 5、分类的评估方法 5.1 精确率和召回率 5.2…