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: String
    
    static 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.preview
    
    func 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! Cell
        cell.layer.cornerRadius = 15.0
        cell.label.text = items[indexPath.row].title
        return 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 = indexPath
        
        let 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 = destinationIndexPath
        return .init(operation: .move)
    }

	func collectionView(_ collectionView: UICollectionView, dropSessionDidEnd session: UIDropSession) {        
        guard let srcIndex, let destIndex else { return }
        
        swapItems(from: srcIndex, to: destIndex)
        
        self.srcIndex = nil
        self.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 = self
    collectionView.dropDelegate = self
    collectionView.dragDelegate = self
    collectionView.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 = nil
	    self.destIndex = nil
	}
}

再次运行 App 看一下:

在这里插入图片描述

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

总结

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

感谢观赏,再会!😎

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

YOLOv9来了! 使用可编程梯度信息学习你想学的内容, v7作者新作!【文献速读】

YOLOv9文献速读,本文章使用 GPT 4.0 和 Ai PDF 工具完成。 文章地址:https://arxiv.org/pdf/2402.13616.pdf 文章目录 文章简介有哪些相关研究?如何归类?谁是这一课题在领域内值得关注的研究员?论文试图解决什么问题&a…

实现律所高质量发展-Alpha法律智能操作系统

律师行业本质上属于服务行业,而律师团队作为一个独立的服务单位,应当包含研发、市场、销售、服务等单位发展的基础工作环节。但现实中,很多律师团队其实并没有区分这些工作。鉴于此,上海市锦天城律师事务所医药大健康行业资本市场…

2.22 day3、4 QT

完善对话框,点击登录对话框,如果账号和密码匹配,则弹出信息对话框,给出提示"登录成功”,提供一个Ok按钮,用户点击Ok后,关闭登录界面,跳转到其他界面 如果账号和密码不匹配&…

MIT-6.824-Lab2,Raft部分笔记|Use Go

文章目录 前记Paper6:RaftLEC5、6:RaftLAB22AtaskHintlockingstructureguide设计与编码 2BtaskHint设计与编码 2CtaskHint question后记 LEC5:GO, Threads, and Raftgo threads技巧raft实验易错点debug技巧 前记 趁着研一考完期末有点点空余…

十四、图像几何形状绘制

项目功能实现&#xff1a;矩形、圆形、椭圆等几何形状绘制&#xff0c;并与原图进行相应比例融合 按照之前的博文结构来&#xff0c;这里就不在赘述了 一、头文件 drawing.h #pragma once#include<opencv2/opencv.hpp>using namespace cv;class DRAWING { public:void…

“最会写”的中文大模型Weaver来了,中文创意写作能力超GPT-4

分享&#xff5c; Weaver ChatGPT等通用大模型支持的功能成百上千&#xff0c;但是对于普通日常用户来说&#xff0c;智能写作一定是最常见的&#xff0c;也是大模型最能真正帮上忙的使用场景之一。尽管大模型经常能写出看起来像模像样的文字&#xff0c;但是大多数情况下内容…

详细·Kubeadm安装

目录 实验前准备部署K8S集群初始化kubeadm&#xff08;只需要master做&#xff09;部署网络插件flannel测试 pod 资源创建 测试访问部署Dashboard&#xff08;master01&#xff09;浏览器访问 实验前准备 master&#xff1a;192.168.188.11 node01&#xff1a;192.168.188.13 …

Code Composer Studio (CCS) - 全局搜索功能

Code Composer Studio [CCS] - 全局搜索功能 1. Ctrl H&#xff0c;全局搜索功能References 1. Ctrl H&#xff0c;全局搜索功能 References [1] Yongqiang Cheng, https://yongqiang.blog.csdn.net/

如何用代理IP防止被泄露真实IP地址?

随着互联网的普及&#xff0c;我们的网络行为越来越离不开IP地址。然而&#xff0c;由于一些不法分子利用IP地址进行网络攻击、窃取个人信息等行为&#xff0c;保护我们的真实IP地址变得尤为重要。代理IP地址是一种隐藏真实IP地址的方法&#xff0c;通过使用代理服务器来中转网…

Cartographer 栅格地图更新

栅格地图更新过程 首先来了一帧雷达数据&#xff0c;对应到每一个栅格点&#xff0c;即观测得到该栅格点是occupied或者是Free。 在cartographer中&#xff0c;使用CorrespondenceCostValue&#xff08;整数表示的空闲概率&#xff09;表示栅格状态&#xff0c;所以现在的目的就…

学习鸿蒙背后的价值?星河版开放如何学习?

现在是2024年&#xff0c;华为在1月18开展了鸿蒙千帆起仪式发布会。宣布了鸿蒙星河版&#xff0c;并对开发者开放申请&#xff0c;此次发布会主要是说明了&#xff0c;鸿蒙已经是全栈自研底座&#xff0c;鸿蒙星河版本的编程语言改为ArkTS/仓颉&#xff0c;内核改为鸿蒙原生内核…

5.网络游戏逆向分析与漏洞攻防-游戏网络架构逆向分析-测试需求与需求拆解

内容参考于&#xff1a;易道云信息技术研究院VIP课 上一个内容&#xff1a;模拟游戏登陆器启动游戏并且完成注入 首先正常分析软件程序有没有漏洞&#xff0c;需要通过它的操作侵入&#xff0c;比如买东西&#xff0c;就通过买东西的按钮它背后有源代码就看源代码&#xff0c…

开启MySQL远程访问权限,允许远程连接

1、登录mysql数据库 mysql -u root –p 如果端口不是默认的3306&#xff0c;此处端口为3308&#xff0c;使用该指令&#xff1a; mysql –u root –port3308 -p 2、输入密码&#xff1a; 3、使用mysql&#xff0c;查看user表 use mysql; 4、查询user表&#xff0c;root账…

SpringBoot启动报错:Failed to load property source from ‘file:/D:.....

SpringBoot启动报错&#xff1a;Failed to load property source from file:/D:… SpringBoot启动爆如图的错误 2024-02-22 20:57:42.865 ERROR 23024 --- [ restartedMain] o.s.boot.SpringApplication : Application run failedjava.lang.IllegalStateExce…

Centos7环境下安装Docker详细步骤

目录 0.前言 1.卸载旧版 2.配置Docker的yum库 3.安装Docker 4.启动和校验 5.配置镜像加速 5.1.注册阿里云账号 5.2.开通镜像服务 5.3.配置镜像加速 0.前言 环境&#xff1a;Centos7 推荐&#xff1a;买个Centos7阿里或者腾讯云服务&#xff0c;这样就可以不用安装虚…

智慧养老驿站健康监测系统场景需求和技术要求

场景建设需求 1.场景建设核心任务目标 搭建养老驿站的健康检测系统平台&#xff0c;以智慧化手段整合数据、视屏、物联设备&#xff0c;全方位提升对政府、老人、养老机构、服务机构、服务人员等对象的服务支撑能力&#xff0c;赋能居家养老、社区养老、机构养老等多种养老模…

消息中间件之RocketMQ源码分析(十二)

Namesrv启动流程 Broker启动流程 BrokerStartup.java类主要负责为真正的启动过程做准备&#xff0c;解析脚本传过来的参数&#xff0c;初始化Broker配置&#xff0c;创建BrokerController实例等工作。BrokerController.java类是Broker的掌控者&#xff0c;它管理和控制Broker的…

2.21 Qt day2 菜单栏/工具栏/状态栏/浮动窗口、UI界面、信号与槽

思维导图 使用手动连接&#xff0c;将登录框中的取消按钮使用qt4版本的连接到自定义的槽函数中&#xff0c;在自定义的槽函数中调用关闭函数 将登录按钮使用qt5版本的连接到自定义的槽函数中&#xff0c;在槽函数中判断ui界面上输入的账号是否为"admin"&#xff0c;…

【Java 面试题】MySQL与Redis 如何保证双写一致性

目录 方案一:延时双删方案二: 删除缓存重试机制方案三:读取biglog异步删除缓存系列文章版本记录方案一:延时双删 延时双删流程 先删除缓存再更新数据库休眠一会(比如1秒),再次删除缓存。这个休眠一会,一般多久呢?都是1秒? 这个休眠时间 = 读业务逻辑数据

计算机网络:传输层知识点汇总

文章目录 一、传输层概述二、UDP协议三、TCP协议特点和TCP报文段格式四、TCP连接管理五、TCP可靠传输六、TCP流量控制七、TCP拥塞控制 一、传输层概述 我们通信的时候&#xff0c;虽然都说是主机之间的通信&#xff0c;但实际是主机之间的进程和进程通信。比如我和你发微信&am…