iOS学习笔记之UICollectionView小结


这里就是对UICollectionView的一个个人总结,不喜勿喷,如有不妥之处,望请指正🙏🙏🙏


简介

UICollectionView是iOS 6新引进的API,用于展示集合视图,布局更加灵活,可以显示多列布局,具有高度定制内容展示样式的能力,用法类似UITableView和UITableViewController类似。

重要组成部分

UICollectionViewCell

生命周期

1
iOS 6 ~ iOS 9

当屏幕外有一个cell准备划入屏幕即将显示的时候,会将cell通过重用标识符从reuse队列里取出来,然后会调用func prepareForReuse()(cell上边缘马上进入屏幕的时候调用),这个方法会重制cell,再滑动的话,就会调用func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell方法了,这个方法里面就是开发者用data model填充cell,把cell返回给系统,当cell马上就要进入屏幕的时候,就会调用func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath),这时候就是App最后一次为cell进入屏幕做准备工作的机会了,执行完该方法,cell就进入屏幕中了。当滑动屏幕,cell完全离开屏幕之后,就会调用func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath)方法。

1
iOS 10 ~  *

当滑动屏幕的时候,需要一个cell时,会将cell痛过重用标识符从reuse队列取出来,并调用func prepareForReuse()方法(当cell还没有进入屏幕的时候,就已经提前调用了,这是跟iOS 10 之前的不同之处,也就是说iOS 10的时候cell的整个生命周期都被提前了),再滑动的时候,就会跟iOS 10 之前一样去调用cellForItemAtIndexPath去创建以及填充data model,并把cell返回给系统,同样的因为在之前生命周期提前了,所以这个方法也较之iOS 10 之前调用的要早,不同之处在后面,iOS 10 在调用func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath)方法时,遵循的原则也就是什么时候显示cell,什么时候再去调用,后面基本和iOS10 之前一致了,还有个重大的不同之处就是iOS 10会把滑出屏幕的Cell保持一段时间,当用户滑动太快,想重新滑动回去的时候,cell不需要重新走通过重用标识符获取什么之类的路了,直接调用func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath)这个方法就行了。

UICollectionViewFlowLayout

我们可以通过修改UICollectionViewFlowLayout的属性来实现一些简单的UICollectionView的样式。

属性介绍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// UICollectionViewCell之间的最小行间距,默认为0,实际值只能比该值大
var minimumLineSpacing: CGFloat
// UICollectionViewCell之间的最小列间距,实际值只能比这个值大
var minimumInteritemSpacing: CGFloat
// UICollectionViewCell的大小
var itemSize: CGSize
// 从iOS 8 开始支持的,预估cell的大小,用于适应动态计算item的大小,默认为CGSize.zero,如果想用这个属性的话,必须在自定义cell中实现`func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes`方法
var estimatedItemSize: CGSize
// UICollectionView的滚动方向,默认为垂直方向
open var scrollDirection: UICollectionView.ScrollDirection
// UICollectionView的Header大小
open var headerReferenceSize: CGSize
// UICollectionView的Footer大小
open var footerReferenceSize: CGSize
// UICollectionView中区的内容偏移
open var sectionInset: UIEdgeInsets
// 从iOS 9 开始,设置header或者footer是否悬浮,true为悬浮,类似tableView的区头悬浮效果
var sectionHeadersPinToVisibleBounds: Bool
var sectionFootersPinToVisibleBounds: Bool

与UITableView的区别

相同点

  1. 都是通过Delegates和Data Sources进行驱动的,因此使用的时候都必须实现数据源和代理协议方法;

  2. 在性能上都是利用重用标示来优化循环利用。

不同点

  1. UITableView在初始化的时候只需要传入Frame,然后系统会帮助开发者布局UITableView的cell,不需要开发者而外的处理。而UICollectionView在初始化的时候必须传入布局样式(UICollectionViewLayout),然后系统根据布局样式来进行Cell的布局,当然系统也提供并实现了一个布局样式:UICollectionViewFlowLayout(大多数需求出来之后都不能直接用这个布局样式,,有时候更是麻烦的一腿,囧~);

  2. UITableView的滑动方向只能是垂直的(当然也能水平,但是还不如直接用UICollectionView替换),UICollectionView的滑动方向可以是垂直方向也可以是水平方向;

  3. UICollectionView的cell必须要先用重用标识符注册,而UITableView不用。

常用说明

UICollectionViewDelegate

支持版本:iOS 6.0 以上
用处:
是否必须实现:否

1
func collectionView(_ collectionView: UICollectionView, shouldHighlightItemAt indexPath: IndexPath) -> Bool

支持版本:iOS 6.0 以上
用处:
是否必须实现:否

1
func collectionView(_ collectionView: UICollectionView, didHighlightItemAt indexPath: IndexPath)

支持版本:iOS 6.0 以上
用处:
是否必须实现:否

1
func collectionView(_ collectionView: UICollectionView, didUnhighlightItemAt indexPath: IndexPath)

支持版本:iOS 6.0 以上
用处:指定cell是否可选中
是否必须实现:否

1
func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool

支持版本:iOS 6.0 以上
用处:常用于用户在多选择情况下点击已经选中的单元格
是否必须实现:否

1
func collectionView(_ collectionView: UICollectionView, shouldDeselectItemAt indexPath: IndexPath) -> Bool

支持版本:iOS 6.0 以上
用处:用于用户选中某一个单元格
是否必须实现:否

1
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)

支持版本:iOS 6.0 以上
用处:用于用户取消选中某一单元格
是否必须实现:否

1
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath)

支持版本:iOS 8.0 以上
用处:
是否必须实现:否

1
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath)

支持版本:iOS 8.0 以上
用处:
是否必须实现:否

1
func collectionView(_ collectionView: UICollectionView, willDisplaySupplementaryView view: UICollectionReusableView, forElementKind elementKind: String, at indexPath: IndexPath)

支持版本:iOS 6.0 以上
用处:
是否必须实现:否

1
func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath)

支持版本:iOS 6.0 以上
用处:
是否必须实现:否

1
func collectionView(_ collectionView: UICollectionView, didEndDisplayingSupplementaryView view: UICollectionReusableView, forElementOfKind elementKind: String, at indexPath: IndexPath)

支持版本:iOS 7.0 以上
用处:当需要转换布局的时候调用
是否必须实现:否

1
func collectionView(_ collectionView: UICollectionView, transitionLayoutForOldLayout fromLayout: UICollectionViewLayout, newLayout toLayout: UICollectionViewLayout) -> UICollectionViewTransitionLayout

支持版本:iOS 9.0 以上
用处:
是否必须实现:否

1
func collectionView(_ collectionView: UICollectionView, canFocusItemAt indexPath: IndexPath) -> Bool

支持版本:iOS 9.0 以上
用处:
是否必须实现:否

1
func collectionView(_ collectionView: UICollectionView, shouldUpdateFocusIn context: UICollectionViewFocusUpdateContext) -> Bool

支持版本:iOS 9.0 以上
用处:
是否必须实现:否

1
func collectionView(_ collectionView: UICollectionView, didUpdateFocusIn context: UICollectionViewFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator)

支持版本:iOS 9.0 以上
用处:
是否必须实现:否

1
func indexPathForPreferredFocusedView(in collectionView: UICollectionView) -> IndexPath?

支持版本:iOS 9.0 以上
用处:
是否必须实现:否

1
func collectionView(_ collectionView: UICollectionView, targetIndexPathForMoveFromItemAt originalIndexPath: IndexPath, toProposedIndexPath proposedIndexPath: IndexPath) -> IndexPath

支持版本:iOS 9.0 以上
用处:
是否必须实现:否

1
func collectionView(_ collectionView: UICollectionView, targetContentOffsetForProposedContentOffset proposedContentOffset: CGPoint) -> CGPoint // customize the content offset to be applied during transition or update animations

支持版本:iOS 11.0 以上
用处:
是否必须实现:否

1
func collectionView(_ collectionView: UICollectionView, shouldSpringLoadItemAt indexPath: IndexPath, with context: UISpringLoadedInteractionContext) -> Bool


UICollectionViewDataSource(UICollectionView的数据源方法)

支持版本:iOS 6.0 以上
用处:确定某个区有多少个单元格
是否必须实现:是

1
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int

支持版本:iOS 6.0 以上
用处:根据类型返回注册过的cell,返回的cell必须是是注册过的,且重用标识符与注册时的要一模一样。
是否必须实现:是

1
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell

支持版本:iOS 6.0 以上
用处:确定当前CollectionView有多少个区,默认为1
是否必须实现:否

1
func numberOfSections(in collectionView: UICollectionView) -> Int

支持版本:iOS 6.0 以上
用处:根据类型返回注册过的头或者尾视图,返回的视图必须是是注册过的,且重用标识符与注册时的要一模一样。
是否必须实现:否

1
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView

支持版本:iOS 9.0 以上
用处:
是否必须实现:否

1
func collectionView(_ collectionView: UICollectionView, canMoveItemAt indexPath: IndexPath) -> Bool

支持版本:iOS 9.0 以上
用处:
是否必须实现:否

1
func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath)

支持版本:iOS 6.0 以上
用处:
是否必须实现:否

1
func indexTitles(for collectionView: UICollectionView) -> [String]?

支持版本:iOS 6.0 以上
用处:
是否必须实现:否

1
func collectionView(_ collectionView: UICollectionView, indexPathForIndexTitle title: String, at index: Int) -> IndexPath

UICollectionViewDelegateFlowLayout

支持版本:iOS 6.0 以上
用处:单元格的大小
是否必须实现:否

1
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize

支持版本:iOS 6.0 以上
用处:设置指定区内的内边距
是否必须实现:否

1
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets

支持版本:iOS 6.0 以上
用处:设置指定区内的cell的最小行间距
是否必须实现:否

1
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat

支持版本:iOS 6.0 以上
用处:设置指定区内的cell的最小列间距
是否必须实现:否

1
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat

支持版本:iOS 6.0 以上
用处:设置指定区的header的大小
是否必须实现:否

1
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize

支持版本:iOS 6.0 以上
用处:设置指定区的footer的大小
是否必须实现:否

1
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize

自定义UICollectionViewLayout

简单说明

在实际开发中,作为iOS开发者可能在用到UICollectionView的时候,大多数都要使用自定义UICollectionViewLayout来定制一些极其能让用户接受的展示UI,确实如此。
在自定义UICollectionViewLayout的时候我觉得可以分成3个步骤:
1.重写UICollectionViewLayout的prepareLayout,在该方法里事先将计算好的一些必要的布局信息,并且存储起来;
2.基于prepareLayout方法中的布局信息,使用collectionViewContentSize方法返回UICollectionView的内容尺寸;
3.使用layoutAttributesForElementsInRect方法返回指定的区域的cell、Supplementary View和Decoration View的布局属性。

自定义UICollectionViewLayout的Demo


SFFocusViewLayout

CollectionViewSlantedLayout

RFQuiltLayout

CollectionKit


开发过程中遇到的问题以及解决方案

问题一:

报错信息:

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'UICollectionView must be initialized with a non-nil layout parameter'

错误原因分析:
UICollectionView缺少布局对象。
解决方案
给UICollectionView指定布局对象(UICollectionViewLayout)


问题二:

报错信息:

Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'could not dequeue a view of kind: UICollectionElementKindCell with identifier cell - must register a nib or a class for the identifier or connect a prototype cell in a storyboard

错误原因分析:
没有注册cell,系统不知道按照哪种方式去创建cell。
解决方案:
给CollectionView注册cell,可以通过如下几种方法注册cell:

open func register(_ cellClass: AnyClass?, forCellWithReuseIdentifier identifier: String)
open func register(_ nib: UINib?, forCellWithReuseIdentifier identifier: String)

open func register(_ viewClass: AnyClass?, forSupplementaryViewOfKind elementKind: String, withReuseIdentifier identifier: String)

open func register(_ nib: UINib?, forSupplementaryViewOfKind kind: String, withReuseIdentifier identifier: String)


问题三:

报错信息:

Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'the cell returned from -collectionView:cellForItemAtIndexPath: does not have a reuseIdentifier - cells must be retrieved by calling -dequeueReusableCellWithReuseIdentifier:forIndexPath:

错误原因分析:
在返回cell的时候,没有使用重用标识符,必须要通过dequeueReusableCellWithReuseIdentifier:forIndexPath:方法来创建。

解决方案:

在调用func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell方式时,要通过collectionView注册cell的时使用的重用标识符来获取cell。


问题四:

报错信息:

Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'invalid nib registered for identifier (ViewCell) - nib must contain exactly one top level object which must be a UICollectionReusableView instance'

错误原因分析:
在使用nib来注册cell的时候发现,在nib文件中有两个或者以上的cell。

解决方案:
每一个UICollectionViewCell的Nib文件有且只能有一个cell样式,分离多余的Cell,只保留Nib中只有一个cell样式。


问题五:

报错信息:

Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'the view returned from -collectionView:viewForSupplementaryElementOfKind:atIndexPath (UICollectionElementKindSectionHeader,<NSIndexPath: 0xa960f426cdfc3d50> {length = 2, path = 0 - 0}) was not retrieved by calling -dequeueReusableSupplementaryViewOfKind:withReuseIdentifier:forIndexPath: or is nil (<faceslink.PbCommitFooterView: 0x10f9e5a50; baseClass = UICollectionReusableView; frame = (0 0; 0 0); layer = <CALayer: 0x2824fd8e0>>)'

错误原因分析:
出现这种情况的原因是实现了UICollectionElementKindSectionHeader大小的代理,但是未注册UICollectionElementKindSectionHeader,所以colleciontView未找到UICollectionElementKindSectionHeader
解决方案:
注册UICollectionElementKindSectionHeader


😊😊😊😊😊😊
关于UICollectionView开发过程中遇到的问题,会在后续工作中继续发现,继续补充。


Tip:如有遗漏的地方,还烦请大神指正补充。