20、UICollectionView的使用

实现垂直方向的单列表来说,使用UITableView足以;
若是需要构建横向滑动列表、gridView等直线型布局,则使用UICollectionView+UICollectionViewFlowLayout搭建最合适;
更复杂的布局,则可以使用UICollectionView+自定义Layout来实现。

UICollectionView的设计理念

UICollectionView是内容和布局完全分离的设计,UICollectionView负责界面部分,UICollectionViewlayout负责UICollectionView的布局,具体的每个元素的布局就交给UICollectionViewLayoutAttributes,另外attributes也是可以进行扩展的,比如需要加入maskView或者改变layer的属性,都可以在attributes里面进行自己的定义。

UICollectionView的工作流程

UICollectionView显示内容时,先从数据源获取cell,然后交给UICollectionView。再从UICollectionViewLayout获取对应的layout attributes(布局属性)。最后,根据每个cell对应的layout attributes(布局属性)来对cell进行布局,生成了最终的界面。而用户交互的时候,都是通过Delegate来进行交互。当然,上面只是布局cell,但是UICollectionView内部还有Supplementary ViewDecoration View,也可以对其进行布局。

UICollectionViewFlowLayout的用法

UICollectionViewFlowLayout常用的属性

  • itemSize:如果cell的大小是固定的,应该直接设置此属性,就不用实现设置大小的代理方法了。
  • minimumLineSpacing:行之间的最小间距。
  • minimumInteritemSpacing:最小cell之间的间距。
  • sectionInset:每一组的内容缩进
  • scrollDirection:设置滚动方向
  • headerReferenceSize:header参考大小
  • footerReferenceSize:footer参考大小
  • sectionHeadersPinToVisibleBounds: 顶部是否悬停
  • sectionFootersPinToVisibleBounds: 底部是否悬停

常用方法

prepare

prepare()是专门用来准备布局的,在prepare方法里面我们可以事先就计算后面要用到的布局信息并存储起来,防止后面方法多次计算,提高性能。例如,我们可以在此方法就计算好每个cell的属性、整个CollectionView的内容尺寸等等。此方法在布局之前会调用一次,之后只有在调用invalidateLayout、shouldInvalidateLayoutForBoundsChange:返回YES和UICollectionView刷新的时候才会调用。

//预加载布局属性
    override func prepare() {
        super.prepare()
    }

layoutAttributesForItem

返回对应的indexPath的cell的attributes。

  override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {

}

layoutAttributesForSupplementaryView

返回对应的header和footer的attributes

layoutAttributesForSupplementaryView

collectionViewContentSize

collectionView的size 这个size不是可视范围的size是整个collectionView的size

layoutAttributesForElements

返回在rect范围内所有cell footer和head的attribute

   override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        
    }

自定义FlowLayout

理解Layout的布局过程

invalidateLayout 会使当前的 layout 无效,并触发 layout 的更新,会强迫 layout 对象重新计算 layout 属性。与 reloadData 不一样,当 data source 中的数据发生改变,适合用 reloadData 方法。
在布局过程中,会按顺序调用一下函数,可以在这些方法中计算 item 的位置信息。

  • prepare 方法来执行一些准备工作,可以进行一些 layout 布局需要的计算等
  • collectionViewContentSize 方法用来返回整个内容的 content size 大小
  • layoutAttributesForElements: 方法用来返回在指定的矩形区域内的 cells 的属性等信息

prepare方法是为确定布局中各 cellview 位置做计算,需要在此方法中算出足够的信息以供后续方法计算内容区域的整体 sizecollection view 使用 content size 以正确地配置 scroll view。比如 content size 长宽均超过屏幕的话,水平与竖直方向的滚动都会被 enable。基于当前滚动位置,collection view 会调用 layoutAttributesForElements:方法以请求特定 rect (有可能是也可能不是可见 rect)中 cellview 的属性。到此,core layout process 已经结束了。

layout 结束之后,cellsviews 的属性在你或者 collection view invalidate布局之前都不会变。调用 invalidateLayout 方法会导致新的一次 layout process 开始,以调用 prepare方法开始。collection view 可以在滚动的过程中自动 invalidate 布局,用户滚动内容过程中,collection view调用layoutshouldInvalidateLayoutForBoundsChange: 方法,如果返回值为 YESinvalidate 布局。(但需要知道的是,invalidateLayout 并不会马上触发 layout update process,而是在下一个view更新周期中,collection view 发现 layout 已经 dirty 才会去更新)

创建布局属性Layout Attributes

自定义 layout 需要返回一个 UICollectionViewLayoutAttributes 类的对象,这些对象可以在很多不同的方法中创建,但创建时间可以根据具体情况决定。
如果 collection view 不会处理上千个 items 时,则 prepareLayout 创建会比用户滚动过程中用到时在计算更高效,因为创建的属性可以缓存起来。如果计算所有属性并缓存起来所带来的性能消耗比请求时在计算属性的消耗更大,则可以在请求的时候在计算相关属性。
UICollectionViewLayoutAttributes的属性:

  • frame
  • bounds
  • center
  • size
  • transform3D
  • transform
  • alpha
  • zIndex
  • hidden

创建 UICollectionViewLayoutAttributes 类对象时,可以使用一些方法:

  • init(forCellWithIndexPath indexPath: NSIndexPath)
  • init(forSupplementaryViewOfKind:withIndexPath:)
  • init(forDecorationViewOfKind:withIndexPath:)

view 的类型不同,必须使用正确的类方法,因为 collection view 会根据这些信息向 data source 对象请求适当类型的 view,使用错误的方法在错误的地方创建错误的 view

创建每个属性对象后,要将相应的 view 的相关属性设置上。最基本的要设置 view 的 size 和 position 信息。如果布局中有 view 重叠了,需要配置正确的 zIndex 属性来维持有序的状态。其他属性可以控制 cell 或 view 的外观及可见性。

准备Layout

在一个布局周期中,首先会调用 prepareLayout 方法,可以来执行一些准备工作,可以进行一些 layout 布局需要的计算等,可以存储一些 layout attributes 信息。

给定矩形中的 items 布局属性

layout process 的最后,collection view 会调用 layoutAttributesForElementsInRect: 方法,对于一个大的可滚动内容区域,collection view 可能只会请求当前可见的那部分区域中的所有 items 属性。这个方法支持获取任意 rectitems 的信息,因为有可能在插入及删除时做动画效果。

layoutAttributesForElementsInRect: 方法实现需要尊重如下的步骤:

  • 遍历 prepareLayout 方法产生的数据以访问缓存的属性或创建新的属性
  • 检查每个 item 中的 frame 以确定是否与 layoutAttributesForElementsInRect: 方法中指定的 rect 有重叠部分
  • 对每个重叠的 item ,添加一个对应的 UICollectionViewLayoutAttributes 对象到一个数组中
  • 返回布局属性的数组给 collection view

不仅要记住缓存layout信息能够带来性能提升,也要记住不断重复为cells创建新layout属性的计算代价是十分昂贵的,足以影响到app的性能。当collection view管理的items量很大时,采用在请求时创建layout属性的方式是十分合理的。

按需提供布局属性

collection view 会在正常的 layout 过程之外周期性的让你提供单个 itemslayout 对象。比如为某 item 配置插入和删除对话。通过以下方法提供信息:

  • layoutAttributesForItemAtIndexPath:
  • layoutAttributesForSupplementaryViewOfKind:atIndexPath:
  • layoutAttributesForDecorationViewOfKind:atIndexPath:

layoutAttributesForItemAtIndexPath: 所有自定义的 layout 必须重写的方法。
当返回属性时,不应该更新这些 layout 属性,如果需要改变 layout 信息,调用 invalidateLayout 在接下来的 layout 周期中更新这些信息。
两种方式设置 collection viewlayout 为自定义的 layout

  • 一种方式在 storyboard 文件中,在 Attributes inspector 中设置 LayoutFlow 改为 Custom
  • 另外一种直接代码设置 self.collectionView.collectionViewLayout = [[MyCustomLayout alloc] init];

插入和删除动画

插入新的 cell 的时候,collection view 会询问 layout 对象提供一组初始化属性用于动画,结束属性就是默认的位置、属性等。类似的,当删除一个 cell 的时候,collection view 会询问 layout 对象提供一组终值属性用于动画,初始属性默认的 indexPath 位置等。
在这里插入图片描述
当插入 item 的时候,layout 对象需要提供正在要被插入的 item 的初始化 layout 信息。在此例中, layout 先将 cell 的初始位置位置到 collection view 的中间,并将 alpha 设为0,动画期间,此 cell 会渐渐出现并移动到右下角。参考代码:

- (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath {
   UICollectionViewLayoutAttributes* attributes = [self layoutAttributesForItemAtIndexPath:itemIndexPath];
   attributes.alpha = 0.0;
 
   CGSize size = [self collectionView].frame.size;
   attributes.center = CGPointMake(size.width / 2.0, size.height / 2.0);
   return attributes;
}

需要注意的是,上述代码在插入 cell 的时候所有的 cell 都会添加此插入的动画,若只想对插入的 item 做插入动画,可以检查 indexPath 是否与传入的prepareForCollectionViewUpdates: 方法的 item 的 indexPath 匹配,并且只有在匹配的时候才进行动画,否则只返回 super 的initialLayoutAttributesForAppearingItemAtIndexPath:. 方法。

override func prepareForCollectionViewUpdates(updateItems: [UICollectionViewUpdateItem]) {
        super.prepareForCollectionViewUpdates(updateItems)
        insertIndexPath = [NSIndexPath]()
        deleteIndexPath = [NSIndexPath]()
        for update in updateItems {            
            switch update.updateAction {
            case .Insert:
                insertIndexPath.append(update.indexPathAfterUpdate!)
            case .Delete:
                deleteIndexPath.append(update.indexPathBeforeUpdate!)
            default:
                print("error")
            }            
            if update.updateAction == UICollectionUpdateAction.Insert {
                
            }
        }
        
    }

delete 动画与插入类似,需要提供正确的 final layout 属性。

提升 layout 的滚动体验

当滚动相关的 touch 事件结束后,scrollview 会根据当前的 speed 和减速状况决定最终会停在哪个偏移。一旦 collection view 知道这个位置后,它就会询问 layout 对象是否修改这个位置,通过调用 targetContentOffset(forProposedContentOffset:withScrollingVelocity:) 。由于是在滚动过程中调用此方法,所以自定义 layout 可以改变滚动的停止位置。
在这里插入图片描述
假如 collection view 开始于(0,0),且用户向左滑动,collection view 计算出滚动原本会停在如下的位置,这个值是 “proposed” contentoffset 值。自定义 layout 可以改变这个值,以确保滚动停下的时候,某个 item 正好停留在可见区域的正中间。这个新值会成为新的目标的 content offset,这个值从 targetContentOffsetForProposedContentOffset:withScrollingVelocity: 方法中返回。

override func targetContentOffsetForProposedContentOffset(proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {        
        //计算最终显示的矩形框
        var rect: CGRect = CGRectZero
        rect.origin.y = 0
        rect.origin.x = proposedContentOffset.x
        rect.size = (collectionView!.frame.size)        
        //根据最终的矩形来获得super已经计算好的属性
        let originArray = super.layoutAttributesForElementsInRect(rect)
        let attributes = NSArray(array: originArray!, copyItems: true) as? [UICollectionViewLayoutAttributes]
        //计算collectionView最中心点的x值
        let centerX = proposedContentOffset.x + collectionView!.frame.size.width * 0.5
        //存放做小间距
        var minDelta: CGFloat = CGFloat(MAXFLOAT)
        for attrs in attributes! {
            if abs(minDelta) > abs(attrs.center.x - centerX) {
                minDelta = attrs.center.x - centerX
            }
        }
        //修改原有的偏移量
        return CGPointMake(proposedContentOffset.x + minDelta, proposedContentOffset.y)
        
    }

改进自定义布局的建议

  • items 数量较少时,数百个或者 items layout 信息变化较小时,可以在 prepareLayout 中创建并缓存 UICollectionViewLayoutAttributes 布局属性对象信息;当items 数量达到上千个时候,需要衡量缓存和重新计算两种方式的性能差异
  • 禁止继承 UICollectionView
  • 不要在 layoutAttributesForElementsInRect: 方法中调用 UCollectionViewvisibleCells方法,因为其实这个调用是转化成了向layout对象请求visible cells 方法。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值