干货之UICollectionViewFlowLayout自定义排序和拖拽手势
使用UICollectionView,需要使用UICollectionViewLayout控制UICollectionViewCell布局,虽然UICollectionViewLayout提供了高度自定义空间,但是对于日常使用显得太繁琐,于是常见使用UICollectionViewFlowLayout。除了提供UITableView类似的协议方法,后者还提供了协议UICollectionViewDelegateFlowLayout <UICollectionViewDelegate>,定义了返回cell尺寸、间距,section的insets,header、footer尺寸等方法。从iOS9开始,UICollectionViewDelegate增加了cell的move相关的协议方法。但是不同尺寸的cell排序效果仍需要自行实现。
为了支持iOS7以上,实现常见不同尺寸cell自定义排序,故实现了一个UICollectionViewFlowLayout的子类,并提供了一个UICollectionViewLayout分类支持自定义拖拽手势。
查阅了一些资料,实现不同尺寸cell自定义排序的文章已经不少,常见都是描述了实现不具有header和footer的独个section的items排序,但是基本都未完整支持UICollectionViewFlowLayout的功能。
我封装了一个子类,完全支持UICollectionViewFlowLayout的功能,并额外提供了设置section背景颜色和所有section的items规则排序的扩展功能,鉴于代码数量,在此记录一下关键的实现过程。
ALWCollectionViewFlowLayout
1.设置section背景颜色
如果设置了UICollectionView的背景色,但是需要不同section显示不同颜色,就只需要自行在子类实现了。
UICollectionView的内容view可以分为三类:SupplementaryView(header和footer),Cell(item),DecorationView(多用在cell下层)
设置section的背景色,可以控制decorationView的背景色来实现。
UICollectionViewLayout提供了一个如下方法
- (void)registerClass:(nullable Class)viewClass forDecorationViewOfKind:(NSString *)elementKind;
可以在子类init方法中,注册一个UICollectionReusableView的子类ALWCollectionReusableView,作为decorationView。在ALWCollectionReusableView中,重载方法- (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes;这里的layoutAttributes对象,具有众多属性,唯独没有一个UIColor。理所当然,再实现一个UICollectionViewLayoutAttributes的子类,增加一个UIColor属性名为backgroundColor,可以设置默认值。然后在applyLayoutAttributes方法中,如下实现:
- (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes
{
[super applyLayoutAttributes:layoutAttributes]; if ([layoutAttributes isMemberOfClass:[ALWCollectionViewLayoutAttributes class]]) {
self.backgroundColor = ((ALWCollectionViewLayoutAttributes *)layoutAttributes).backgroundColor;
}
}
以上,只是注册和设置了decorationView的背景色,但是还未设置装饰view的显示frame和在合适的时机使其生效。
这个时机,就是UICollectionViewFlowLayout的如下两个方法
- (void)prepareLayout;
- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect;
前者是设置属性的时机,后者是使其生效的时机。
A.为了便于动态设置每个section的背景色,提供了一个协议方法
- (UIColor *)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout backgroundColorForSectionAtIndex:(NSInteger)section;
B.计算decorationView尺寸时候,可以根据每个section的首尾item的frame和sectionInset来确定;当同时有Header和footer时候,也可以根据二者来确定。但是需要注意,如果部分header或者footer未实现,在获取布局属性对象时候会为nil
C.在UICollectionViewFlowLayout子类中可使用如下实例方法获取布局属性对象
- (nullable UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath;
- (nullable UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath;
- (nullable UICollectionViewLayoutAttributes *)layoutAttributesForDecorationViewOfKind:(NSString*)elementKind atIndexPath:(NSIndexPath *)indexPath;
但是在UICollectionViewLayout中将会返回nil,可以使用UICollectionViewLayoutAttributes的类方法得到实例
+ (instancetype)layoutAttributesForCellWithIndexPath:(NSIndexPath *)indexPath;
+ (instancetype)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind withIndexPath:(NSIndexPath *)indexPath;
+ (instancetype)layoutAttributesForDecorationViewOfKind:(NSString *)decorationViewKind withIndexPath:(NSIndexPath *)indexPath;
2.自定义不同尺寸的item的排序
UICollectionViewFlowLayout已经很好的展示了等尺寸的item排列,但是不同尺寸的item排列则显得不规则,如果希望在使用父类的完整功能基础上,每排item按照固定间距排列,可以按照如下记录实现。
为了间距规则,所以我的实现前提是固定横向竖向其中一个方向每排的item数量、边长都相等。如下图显示了填充类型的排序演示效果:

大致记录一下实现过程:
A.重载方法- (void)prepareLayout,在方法中得到item、header、footer的布局属性数组(NSMutableArray<UICollectionViewLayoutAttributes*>)
a.以纵向滑动为例,循环累加每个section的header、item、footer高度
b.根据每行item数量决定高度占用数组元素数量,记录每列item垂直方向当前占用的内容高度
c.以填充排序为例,每个item的y方向偏移量由高度占用数组最小元素决定;x方向由sectionInset、item固定的宽度、横向间距、列的索引共同决定;尺寸方面只需要获取itemSize协议方法返回尺寸中的高度
B.重载方法- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect;返回items、header、footer、decorationView的属性数组
C.重载方法- (CGSize)collectionViewContentSize,返回A过程中记录的占用的最大内容尺寸
D.特别注意,不能在super方法返回的数组基础上,再添加Header、footer的布局属性。这样可能会出现如下错误
layout attributes for supplementary item at index path (<NSIndexPath>)
changed from <UICollectionViewLayoutAttributes>
to <UICollectionViewLayoutAttributes>
without invalidating the layout
就是由于布局属性数组中,存在相同indexPath的布局属性对象。
UICollectionViewLayout (DragGesture)
提供支持iOS7以上的拖拽手势,适用于所有子类。iOS9以后,UICollectionView提供了move相关方法,只需要添加手势触发调用相关方向即可。我实现的分类提供了类似的方法,为了不混淆使用,还提供了启用属性,默认关闭。
实现过程:
1.交换init方法,在其中增加collectionView属性的KVO,因为UICollectionView的实例化一般在UICollectionViewLayout之后
2.在UICollectionView实例化后,根据启用属性,添加长按手势和拖动手势到UICollectionView上
3.如果选中了某个item,开始持续关注拖动位置,进入另一个item后,交换二者,实现动画和数据交换。主要涉及UICollectionView的方法
- (void)performBatchUpdates:(void (^ __nullable)(void))updates completion:(void (^ __nullable)(BOOL finished))completion;
4.增加CADisplayLink对象,根据拖动方向和item所处位置,以屏幕刷新频率和预定移动速度,自动移动UICollectionView内容偏移量
效果图:

备注:目前在自定义排序后,拖拽手势效果仍存在短暂闪烁的问题,会持续修复。
猜测问题在重载返回属性数组的方法中,因为使用UICollectionViewFlowLayout默认排序时候,拖拽效果没有问题。
如果有朋友找到问题所在,请留言,非常感谢。
该类库已经在Base项目中更新:https://github.com/ALongWay/base.git
干货之UICollectionViewFlowLayout自定义排序和拖拽手势的更多相关文章
- 拖拽手势和清扫手势冲突时(UIPanGestureRecognizer和UISwipeGestureRecognizer冲突时)
故事发生在这样的情境上:给整个控制器添加了一个拖拽手势,然后又在控制上的每个Cell上加了左滑清扫手势,然后问题来了:只有拖拽手势起作用,而左滑手势没有效果了,然后怎么解决这个问题呢!先上图: 当给整 ...
- iOS开发拓展篇—xib中关于拖拽手势的潜在错误
iOS开发拓展篇—xib中关于拖拽手势的潜在错误 一.错误说明 自定义一个用来封装工具条的类 搭建xib,并添加一个拖拽的手势. 主控制器的代码:加载工具条 封装工具条以及手势拖拽的监听事件 此时运行 ...
- iOS - xib中关于拖拽手势的潜在错误
iOS开发拓展篇—xib中关于拖拽手势的潜在错误 一.错误说明 自定义一个用来封装工具条的类 搭建xib,并添加一个拖拽的手势. 主控制器的代码:加载工具条 封装工具条以及手势拖拽的监听事件 此时运行 ...
- 如何在ScrollView滑动的瞬间禁用拖拽手势
如何在ScrollView滑动的瞬间禁用拖拽手势 效果: 在UIScrollView滑动的瞬间禁用pan手势,可以防止用户按着屏幕不放后导致出现的一些莫须有的bug. // // ViewContro ...
- 使用knockout-sortable实现对自定义菜单的拖拽排序
在开始之前,照例,我们先看效果和功能实现. 关于自定义菜单的实现,这里就不多说了,需要了解的请访问:http://www.cnblogs.com/codelove/p/4838766.html 这里需 ...
- katalon系列十:Katalon Studio自定义关键字之拖拽
Katalon Studio自带关键字“Drag And Drop To Object”,可以在这个网站实践:http://jqueryui.com/droppable/#default 不过“Dra ...
- View拖拽 自定义绑定view拖拽的工具类
由于工作需求,需要用到这种处理方法所以我就写了这个 废话不多说先看效果图 接下来就看代码吧 DragDropManager import android.app.Activity; import an ...
- vue全局自定义指令-元素拖拽
小白我用的是vue-cli的全家桶,在标签中加入v-drap则实现元素拖拽, 全局指令我是写在main.js中 Vue.directive('drag', { inserted: function ( ...
- 14-UIKit(拖拽手势、布局)
目录: 1.手势创建的拖拽方式 2.frame,bounds,transform,center区别 3.触控(touch) 4.布局 5.代码布局 回到顶部 1.手势创建的拖拽方式 创建手势对象,修改 ...
随机推荐
- 阅读《LEARNING HARD C#学习笔记》知识点总结与摘要五
本篇文章主要是总结异步编程的知识点,也是本系列的最后一篇文章,每一个知识点我都有写出示例代码,方便大家理解,若发现有误或不足之处还请指出,由于书中作者对此知识点讲解过于简单,所以在写这篇文章时本人参考 ...
- 浅谈Jquery中的bind(),live(),delegate(),on()绑定事件方式
前言 因为项目中经常会有利用jquery操作dom元素的增删操作,所以会涉及到dom元素的绑定事件方式,简单的归纳一下bind,live,delegate,on的区别,以便以后查阅,也希望该文章日后能 ...
- 打开Windows10网络发现或是文件打印共享
新安装的Windows10,已经设置好的目录共享,用户说访问不了.就连管理员的权限使用盘符加"$"也无法访问.原来Windows10默认情况之下,是把网络发现以及文件打印共享关闭的 ...
- js中局部变量必须用var去声明
js中的变量与其他的脚本语言都是很不一样的,在function中你如果不用var 声明一个变量,那么这个变量将在全局可见,也就相当于创建了全局变量.所以在function中声明变量尽量都是用var来声 ...
- SQL SERVER与C#中数据类型的对应关系
对应关系表 SQL Server2000 http://hovertree.com/menu/sqlserver/ C# CodeSmith 数据类型 取值范围 数据类型 取值范围 空值代替值 数据类 ...
- 速战速决 (3) - PHP: 函数基础, 函数参数, 函数返回值, 可变函数, 匿名函数, 闭包函数, 回调函数
[源码下载] 速战速决 (3) - PHP: 函数基础, 函数参数, 函数返回值, 可变函数, 匿名函数, 闭包函数, 回调函数 作者:webabcd 介绍速战速决 之 PHP 函数基础 函数参数 函 ...
- 线程池之 Callable、Future、FutureTask
java线程中的异步和同步,并不是走路,一定要搞清楚.那么join方法嘛,就是异步变同步.线程阻塞,就再楼下一直等着它想要的状态出现喽.直接上代码,先来看Future获取线程执行结果的使用示例: pu ...
- Scalaz(38)- Free :Coproduct-Monadic语句组合
很多函数式编程爱好者都把FP称为Monadic Programming,意思是用Monad进行编程.我想FP作为一种比较成熟的编程模式,应该有一套比较规范的操作模式吧.因为Free能把任何F[A]升格 ...
- javascript--Function
概述 函数的声明 (1)function命令 函数就是使用function命令命名的代码区块,便于反复调用. function print(){ // ... } 上面的代码命名了一个print函数, ...
- 【Asphyre引擎】冒险岛换装Demo升级到最新版PXL
demo代码 (不包含Sprite代码,要Sprite代码请下载之前那个doudou的demo)