使用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方法中,如下实现:

  1. - (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes
  2. {
  3. [super applyLayoutAttributes:layoutAttributes];
  4.  
  5. if ([layoutAttributes isMemberOfClass:[ALWCollectionViewLayoutAttributes class]]) {
  6. self.backgroundColor = ((ALWCollectionViewLayoutAttributes *)layoutAttributes).backgroundColor;
  7. }
  8. }

以上,只是注册和设置了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的布局属性。这样可能会出现如下错误

  1. layout attributes for supplementary item at index path (<NSIndexPath>)
  2. changed from <UICollectionViewLayoutAttributes>
  3. to <UICollectionViewLayoutAttributes>
  4. 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自定义排序和拖拽手势的更多相关文章

  1. 拖拽手势和清扫手势冲突时(UIPanGestureRecognizer和UISwipeGestureRecognizer冲突时)

    故事发生在这样的情境上:给整个控制器添加了一个拖拽手势,然后又在控制上的每个Cell上加了左滑清扫手势,然后问题来了:只有拖拽手势起作用,而左滑手势没有效果了,然后怎么解决这个问题呢!先上图: 当给整 ...

  2. iOS开发拓展篇—xib中关于拖拽手势的潜在错误

    iOS开发拓展篇—xib中关于拖拽手势的潜在错误 一.错误说明 自定义一个用来封装工具条的类 搭建xib,并添加一个拖拽的手势. 主控制器的代码:加载工具条 封装工具条以及手势拖拽的监听事件 此时运行 ...

  3. iOS - xib中关于拖拽手势的潜在错误

    iOS开发拓展篇—xib中关于拖拽手势的潜在错误 一.错误说明 自定义一个用来封装工具条的类 搭建xib,并添加一个拖拽的手势. 主控制器的代码:加载工具条 封装工具条以及手势拖拽的监听事件 此时运行 ...

  4. 如何在ScrollView滑动的瞬间禁用拖拽手势

    如何在ScrollView滑动的瞬间禁用拖拽手势 效果: 在UIScrollView滑动的瞬间禁用pan手势,可以防止用户按着屏幕不放后导致出现的一些莫须有的bug. // // ViewContro ...

  5. 使用knockout-sortable实现对自定义菜单的拖拽排序

    在开始之前,照例,我们先看效果和功能实现. 关于自定义菜单的实现,这里就不多说了,需要了解的请访问:http://www.cnblogs.com/codelove/p/4838766.html 这里需 ...

  6. katalon系列十:Katalon Studio自定义关键字之拖拽

    Katalon Studio自带关键字“Drag And Drop To Object”,可以在这个网站实践:http://jqueryui.com/droppable/#default 不过“Dra ...

  7. View拖拽 自定义绑定view拖拽的工具类

    由于工作需求,需要用到这种处理方法所以我就写了这个 废话不多说先看效果图 接下来就看代码吧 DragDropManager import android.app.Activity; import an ...

  8. vue全局自定义指令-元素拖拽

    小白我用的是vue-cli的全家桶,在标签中加入v-drap则实现元素拖拽, 全局指令我是写在main.js中 Vue.directive('drag', { inserted: function ( ...

  9. 14-UIKit(拖拽手势、布局)

    目录: 1.手势创建的拖拽方式 2.frame,bounds,transform,center区别 3.触控(touch) 4.布局 5.代码布局 回到顶部 1.手势创建的拖拽方式 创建手势对象,修改 ...

随机推荐

  1. SpringMVC详细示例

    一.SpringMVC基础入门,创建一个HelloWorld程序 0.框架结构 1.首先,导入SpringMVC需要的jar包. 2.添加Web.xml配置文件中关于springmvc的配置 < ...

  2. Navigation Drawer的使用及遇到的问题

    ActionBar的问题 Navigation View是Android Support Library中的一个新的组件,该组件提供类似于Sliding Menu的抽屉功能,在张兴业的博客中有讲解到具 ...

  3. 瀑布式开发、迭代开发、敏捷开发、XP与SCRUM的区别

    瀑布式开发.迭代开发,区别[都属于,生命周期模型]         两者都是一种开发模式,就像设计模式一样,考虑的角度不一样,个人感觉谈不到取代一说.         传统的瀑布式开发,也就是从需求到 ...

  4. winform 判断鼠标是否在按钮控件范围内

    public void MourseLeave() { bool b = this.RectangleToScreen(this.ClientRectangle).Contains(MousePosi ...

  5. 其实Unix很简单

    很多编程的朋友都在网上问我这样的几个问题,Unix怎么学?Unix怎么这么难?如何才能学好?并且让我给他们一些学好Unix的经验.在绝大多数时候,我发现问这些问题的朋友都有两个特点: 1)对Unix有 ...

  6. ComboBoxEdit设置选项值(单选 多选)

    网上搜索的 例子 加 自己的 一点点补充 lookupedit 设置选项值: private void LookUpEditFormTest_Load(object sender, EventArgs ...

  7. Mysql高并发优化

    一.数据库结构的设计 1.数据行的长度不要超过8020字节,如果超过这个长度的话在物理页中这条数据会占用两行从而造成存储碎片,降低查询效率. 2.能够用数字类型的字段尽量选择数字类型而不用字符串类型的 ...

  8. 不可或缺 Windows Native (13) - C++: 标准输入, 标准输出, 字符串内存流

    [源码下载] 不可或缺 Windows Native (13) - C++: 标准输入, 标准输出, 字符串内存流 作者:webabcd 介绍不可或缺 Windows Native 之 C++ 标准输 ...

  9. hibernate----N-N--(人与地点)

    package com.ij34.dao; import java.util.HashSet; import java.util.Set; import javax.persistence.*; @E ...

  10. Oracle数据库建表并用SQL编程分等级

    --创建学生表create table XS_543 ( XH char(6) not null , XM varchar2(20) not null, ZYM varchar2(10), XB ch ...