• 干货之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

  • 相关阅读:
    序列化二叉树
    按之字形顺序打印二叉树
    C#读写文件的方法汇总_C#教程_脚本之家
    c#缓存介绍(转)
    ASP.NET 缓存技术分析
    pickle使用
    python3.4使用文件
    io的常用操作
    manven需要注意点几点
    git中一些常用的命令
  • 原文地址:https://www.cnblogs.com/ALongWay/p/6018334.html
Copyright © 2020-2023  润新知