collectionView基本用法

先写上Demo: Demo链接
 
 
1.collectionView的初级用法
就是最常用的跟tableVIew一样的用法,有一点值得注意的是cell必须注册,不能通过init方法获取,而且UICollectionViewCell的父类不是UIView,是UICollectionReusableView,而该类也被用作section的header和footer,
下面是基本用法和dataSource代理方法
 
delegate和tableView差别不大,这里就不用废话叙述了,有一个点值得注意的是Header和Footer都在一个代理方法中,并且类型通过kind来判断,并且返回的View的类型并不是UIView,而是UICollectionReusableView,跟cell同理都需要先进行注册:
 
然后就是布局
 
如果每一个cell的size并不一样,header也不一样的话,FlowLayout有一些代理方法可以直接用,其实就是CollectionView的代理方法的子类,
注意注意!!!
上面的代理方法不跟tableView相同,上面的只会调用一遍,再次重用的时候就不会调用了,这一点是非常坑的,那怎么样cell能够实时的变化宽高呢,不要急,后面会讲到collectionView的我进阶用法,子类化FlowLayout。
 
 
2.collectionView的二级用法
    这一次主要讲一些不常用的代理方法(自己之前没有用过的,可能以后会有很大帮助)
collectionCell和tableView都有这个高亮状态的属性,并且有相对应的代理方法,应该用不到吧
 
还有就是长按显示菜单的控制
有一点注意的地方是,自定义菜单的点击事件的Target给的是cell而不是controller;如果需要给Controll,那么就需要获取Controller的代理调用
这样就完工了
 
3.collectionView的进阶用法
介绍一下自定义collection布局———FlowLayout
自定义布局主要的布局方法单独抽出来放在了UICollectionViewLayout中,该类是一个抽象类。系统又自定义了一个流式布局,UICollectionViewFlowLayout,我们可以通过定义该类的子类并复写响应的方法来实现自定义流式布局。
下面先介绍一下常用的方法吧
 
下面这两个方法是比较重要的,因为布局基本上都要用到这两个方法
 
 
实战1
给collectionView中的每一个sectionHeader和footer之间的间隙添加背景颜色
要实现下图的效果,每一个section之间的背景UI需要不同
 
需要自定义一个装饰视图(DecorationView) 该View跟header相同,父类都是UICollectionReusableView,只是注册类型和注册对象不一样,装饰视图是由子类化的Layout去注册的。
我的实现思路是先计算出来要展示的区域的frame值,然后根据显示区域和展示区域是否重合来决定是否展示。
一下是我的代码:
 
// 准备好布时事调用,此时 collectionView 的所有属性都已经确定了
- ( void ) prepareLayout{
    [ super prepareLayout ];
    self.itemAttributes = [ NSMutableArray array ];
    [ self registerClass: [ LKReusableView class ] forDecorationViewOfKind:@"LKReusableView" ];
    id<UICollectionViewDelegateFlowLayout> delegate = ( id ) self.collectionView.delegate ;
    // 总共有多少个组
    NSInteger sectionCount = self.collectionView.numberOfSections ;
    // 遍历每一组
    for ( int section = 0 ; section < sectionCount ; section++ ) {
        // 获取每一组最后一个 item 的索引
        NSInteger lastIndex = [ self.collectionView numberOfItemsInSection:section ] - 1 ;
        if ( lastIndex < 0 ) {
            continue ;
        }
        // 取出每一组第一个和最后一个 item 的布局,来计算每一组的高度,
        UICollectionViewLayoutAttributes *firstItemAttr = [ self layoutAttributesForItemAtIndexPath: [ NSIndexPath indexPathForItem:0 inSection:section ]];
        UICollectionViewLayoutAttributes *lastItemAttr = [ self layoutAttributesForItemAtIndexPath: [ NSIndexPath indexPathForItem:lastIndex inSection:section ]];
        // 计算高度需要把间距加上去
        UIEdgeInsets sectionInset = self.sectionInset ;
        // 判断是否有额外改变间距的代理,有的话就更新间距
        if ([ delegate respondsToSelector:@selector ( collectionView:layout:insetForSectionAtIndex: )]) {
            sectionInset = [ delegate collectionView:self.collectionView layout:self insetForSectionAtIndex:section ];
        }
       
        // 获取除了 section 间距外的矩形大小
        //CGRectUnion 获取包含两个 frame 的最小的矩形 frame
        CGRect frame = CGRectUnion ( firstItemAttr.frame , lastItemAttr.frame );
        //x , y ,初始坐标需要包含 inset ,所以要减间距,
        frame.origin.x -= sectionInset.top ;
        frame.origin.y -= sectionInset.left ;
        /*
         宽高不能这么写,因为 item 不可能正好铺满屏幕,如果最后一行(或者一列)只有一个,那么宽度就是这一个的宽高,得到的并不是 collectionView 的宽高
        frame.size.height += ( sectionInset.top + sectionInset.bottom );
        frame.size.width += ( sectionInset.left + sectionInset.right );
         */
        // 判断滚动方向固定宽高
        if ( self.scrollDirection == UICollectionViewScrollDirectionHorizontal ) { // 水平滚动,高度是固定的,宽度不固定
            frame.size.height = self.collectionView.frame.size.height ;
            frame.size.width += ( sectionInset.left + sectionInset.right );
        }else{// 竖直滚动,宽度是固定的,高度不固定
            frame.size.height += ( sectionInset.top + sectionInset.bottom );
            frame.size.width = self.collectionView.frame.size.width ;
        }
       
       
        // 装饰视图(类似于背景视图,如果做一个书架,该 View 可以做书架) 对于装饰视图的 UI 更新,需要覆写自定义的 UICollectionReusableView applyLayoutAttributes 方法进行覆写
        //
        UICollectionViewLayoutAttributes *bgAttrbutes = [ UICollectionViewLayoutAttributes layoutAttributesForDecorationViewOfKind:@"LKReusableView" withIndexPath: [ NSIndexPath indexPathForItem:0 inSection:section ]];
        // 放在最底层
        bgAttrbutes.zIndex = -1 ;
        bgAttrbutes.frame = frame ;
        [ self.itemAttributes addObject:bgAttrbutes ];
       
    }
}
 
 
/**
 * 这个方法的返回值是一个数组(数组里面存放着 rect 范围内所有元素的布局属性)
 * 这个方法的返回值决定了 rect 范围内所有元素的排布( frame
 */
- ( NSArray<UICollectionViewLayoutAttributes *> * ) layoutAttributesForElementsInRect: ( CGRect ) rect{
    NSArray *arr = [ super layoutAttributesForElementsInRect:rect ];
    NSMutableArray *attrs = [ NSMutableArray arrayWithArray:arr ];
    // 判断当前显示的 rect bgView 是否有重叠,有的话就将该布局加入现有布局之中
    for ( UICollectionViewLayoutAttributes *attributes in self.itemAttributes ) {
        if ( !CGRectIntersectsRect ( rect , attributes.frame )) {
            continue ;
        }
        [ attrs addObject:attributes ];
    }
    return attrs ;
   
}
 
同时,在装饰视图里也要实现相应的方法来动态更新UI
 
 
实战2  瀑布流
实现思路:就是找到高度(宽度)最小的那一列(行)进行重新排版,也就是要重新计算他的frame值
1.首先要预设好一些属性
2.在预布局中计算宽度,并计算每一个item的布局
3.返回collectionView的contentSize
 
以上就是一个简单的collectionView的瀑布流的使用,但是还是有一个问题,就是优化的问题,不可能每次都重新计算所有的高度,还有就是多个section的问题,也需要重新计算,还有一个就是水平方向的瀑布流,这些问题等到实际遇到的时候再去讨论吧。
 
 
实战3  电影类App的电影海报居中放大的效果
要实现如下的效果
1.首先需要打开刷新布局的方法
2.计算偏移量,然后利用偏移比例设置cell的缩小比例
 
3.设置分页的效果,(当collectionView停止滚动时候需要停在哪个位置),
 
以上是基本的用法,用过之后会有一些问题,一下是问题以及解决方案
 
1.只能通过手势滑动才能实现cell居中,点击cell无效果
    解决方案:设置点击效果使用scrollToItem的方法,
   于是这样有出现了一个新问题,就是点击之后有滑动动画,但是滑动的过程也可以点击cell,这时动画就是强制停止,
    解决方案,在滑动动画的时候不可点击,滑动减速和结束的时候再开放点击事件
    还有一个新问题,点击当前的cell不触发滑动效果
    解决方案:设置全局变量记录当前的居中的cell,点击的是同一个cell时不触发任何效果
    
代码:
 
2.第一个和最后一个cell不会居中展示,原因是collectionView的最小偏移量>0,小于collectionView.contentoffset的最大值
解决方案:设置contentInset,扩充offset的范围
 
 
实战4  collectionView的编辑功能
1.删除功能,删除和tableView相同,调用delete的方法即可,注意数据也需要相应的删除
 
2.移动功能,这个功能要分为iOS9之后和之前的操作,iOS9之后的话苹果是有提供了一些api,我们可以直接使用这些接口去做移动的操作。
那么我们先看iOS9之后怎么去做的吧!
    collectionView给我们提供了几个方法
    1.[self.collectionView beginInteractiveMovementForItemAtIndexPath:indexPath]
这个方法是开始移动模式,也就是可以开启移动,它是有一个bool类型的返回值,某些特殊的cell是不支持移动,就会返回NO
    2. [self.collectionView updateInteractiveMovementTargetPosition:point];
这个方法是更新移动坐标,可以根据手指在屏幕上的位置去更新cell的位置
    3.[self.collectionView endInteractiveMovement];
这个方法是结束移动,这个时候会调用collectionView里的DataSource方法,代理方法会在下面说到
    4. [self.collectionView cancelInteractiveMovement];
取消移动,这个时候cell会回到原来的初始位置
 
    当移动结束的时候会调用两个dataSource方法
//设置cell是否可以移动到该位置,如果返回NO,那么cell会回到原来的位置
    - (BOOL)collectionView:(UICollectionView *)collectionView canMoveItemAtIndexPath:(NSIndexPath *)indexPath
 
//当一个cell从一个位置将要移动到另一个位置的时候调用该方法,需要在这个方法里面做数据处理,不然会出现数据错乱的现象
- (void)collectionView:(UICollectionView *)collectionView moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath
 
下面是简单的移动步骤
首先要设置一个长按或者滑动的手势,这里我用了滑动手势pan,因为我设置了一个编辑按钮,来判断是否需要移动
 
然后在手势的返回事件中去开启移动模式
 
最后就是处理数据了
以上就是iOS9对应的编辑模式