这里就是对UICollectionView的一个个人总结,不喜勿喷,如有不妥之处,望请指正🙏🙏🙏
1.简介
UICollectionView是iOS 6新引进的API,用于展示集合视图,布局更加灵活,可以显示多列布局,具有高度定制内容展示样式的能力,用法类似UITableView和UITableViewController类似。
-
重要组成部分
-
UICollectionViewCell
- 生命周期
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)
方法。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的样式。
- 属性介绍
// 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
-
2.与UITableView的区别
-
相同点
-
都是通过Delegates和Data Sources进行驱动的,因此使用的时候都必须实现数据源和代理协议方法;
-
在性能上都是利用重用标示来优化循环利用。
-
-
不同点
-
UITableView在初始化的时候只需要传入Frame,然后系统会帮助开发者布局UITableView的cell,不需要开发者而外的处理。而UICollectionView在初始化的时候必须传入布局样式(UICollectionViewLayout),然后系统根据布局样式来进行Cell的布局,当然系统也提供并实现了一个布局样式:
UICollectionViewFlowLayout
(大多数需求出来之后都不能直接用这个布局样式,,有时候更是麻烦的一腿,囧~); -
UITableView的滑动方向只能是垂直的(当然也能水平,但是还不如直接用UICollectionView替换),UICollectionView的滑动方向可以是垂直方向也可以是水平方向;
-
UICollectionView的cell必须要先用重用标识符注册,而UITableView不用。
-
3.常用说明
- UICollectionViewDelegate
支持版本:iOS 6.0 以上
用处:
是否必须实现:否
func collectionView(_ collectionView: UICollectionView, shouldHighlightItemAt indexPath: IndexPath) -> Bool
支持版本:iOS 6.0 以上
用处:
是否必须实现:否
func collectionView(_ collectionView: UICollectionView, didHighlightItemAt indexPath: IndexPath)
支持版本:iOS 6.0 以上
用处:
是否必须实现:否
func collectionView(_ collectionView: UICollectionView, didUnhighlightItemAt indexPath: IndexPath)
支持版本:iOS 6.0 以上
用处:指定cell是否可选中
是否必须实现:否
func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool
支持版本:iOS 6.0 以上
用处:常用于用户在多选择情况下点击已经选中的单元格
是否必须实现:否
func collectionView(_ collectionView: UICollectionView, shouldDeselectItemAt indexPath: IndexPath) -> Bool
支持版本:iOS 6.0 以上
用处:用于用户选中某一个单元格
是否必须实现:否
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
支持版本:iOS 6.0 以上
用处:用于用户取消选中某一单元格
是否必须实现:否
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath)
支持版本:iOS 8.0 以上
用处:
是否必须实现:否
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath)
支持版本:iOS 8.0 以上
用处:
是否必须实现:否
func collectionView(_ collectionView: UICollectionView, willDisplaySupplementaryView view: UICollectionReusableView, forElementKind elementKind: String, at indexPath: IndexPath)
支持版本:iOS 6.0 以上
用处:
是否必须实现:否
func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath)
支持版本:iOS 6.0 以上
用处:
是否必须实现:否
func collectionView(_ collectionView: UICollectionView, didEndDisplayingSupplementaryView view: UICollectionReusableView, forElementOfKind elementKind: String, at indexPath: IndexPath)
支持版本:iOS 7.0 以上
用处:当需要转换布局的时候调用
是否必须实现:否
func collectionView(_ collectionView: UICollectionView, transitionLayoutForOldLayout fromLayout: UICollectionViewLayout, newLayout toLayout: UICollectionViewLayout) -> UICollectionViewTransitionLayout
支持版本:iOS 9.0 以上
用处:
是否必须实现:否
func collectionView(_ collectionView: UICollectionView, canFocusItemAt indexPath: IndexPath) -> Bool
支持版本:iOS 9.0 以上
用处:
是否必须实现:否
func collectionView(_ collectionView: UICollectionView, shouldUpdateFocusIn context: UICollectionViewFocusUpdateContext) -> Bool
支持版本:iOS 9.0 以上
用处:
是否必须实现:否
func collectionView(_ collectionView: UICollectionView, didUpdateFocusIn context: UICollectionViewFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator)
支持版本:iOS 9.0 以上
用处:
是否必须实现:否
func indexPathForPreferredFocusedView(in collectionView: UICollectionView) -> IndexPath?
支持版本:iOS 9.0 以上
用处:
是否必须实现:否
func collectionView(_ collectionView: UICollectionView, targetIndexPathForMoveFromItemAt originalIndexPath: IndexPath, toProposedIndexPath proposedIndexPath: IndexPath) -> IndexPath
支持版本:iOS 9.0 以上
用处:
是否必须实现:否
func collectionView(_ collectionView: UICollectionView, targetContentOffsetForProposedContentOffset proposedContentOffset: CGPoint) -> CGPoint // customize the content offset to be applied during transition or update animations
支持版本:iOS 11.0 以上
用处:
是否必须实现:否
func collectionView(_ collectionView: UICollectionView, shouldSpringLoadItemAt indexPath: IndexPath, with context: UISpringLoadedInteractionContext) -> Bool
- UICollectionViewDataSource(UICollectionView的数据源方法)
支持版本:iOS 6.0 以上
用处:确定某个区有多少个单元格
是否必须实现:是
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
支持版本:iOS 6.0 以上
用处:根据类型返回注册过的cell,返回的cell必须是是注册过的,且重用标识符与注册时的要一模一样。
是否必须实现:是
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
支持版本:iOS 6.0 以上
用处:确定当前CollectionView有多少个区,默认为1
是否必须实现:否
func numberOfSections(in collectionView: UICollectionView) -> Int
支持版本:iOS 6.0 以上
用处:根据类型返回注册过的头或者尾视图,返回的视图必须是是注册过的,且重用标识符与注册时的要一模一样。
是否必须实现:否
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView
支持版本:iOS 9.0 以上
用处:
是否必须实现:否
func collectionView(_ collectionView: UICollectionView, canMoveItemAt indexPath: IndexPath) -> Bool
支持版本:iOS 9.0 以上
用处:
是否必须实现:否
func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath)
支持版本:iOS 6.0 以上
用处:
是否必须实现:否
func indexTitles(for collectionView: UICollectionView) -> [String]?
支持版本:iOS 6.0 以上
用处:
是否必须实现:否
func collectionView(_ collectionView: UICollectionView, indexPathForIndexTitle title: String, at index: Int) -> IndexPath
- UICollectionViewDelegateFlowLayout
支持版本:iOS 6.0 以上
用处:单元格的大小
是否必须实现:否
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
支持版本:iOS 6.0 以上
用处:设置指定区内的内边距
是否必须实现:否
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets
支持版本:iOS 6.0 以上
用处:设置指定区内的cell的最小行间距
是否必须实现:否
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat
支持版本:iOS 6.0 以上
用处:设置指定区内的cell的最小列间距
是否必须实现:否
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat
支持版本:iOS 6.0 以上
用处:设置指定区的header的大小
是否必须实现:否
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize
支持版本:iOS 6.0 以上
用处:设置指定区的footer的大小
是否必须实现:否
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize
4.自定义UICollectionViewLayout
4.1 简单说明
在实际开发中,作为iOS开发者可能在用到UICollectionView的时候,大多数都要使用自定义UICollectionViewLayout来定制一些极其能让用户接受的展示UI,确实如此。
在自定义UICollectionViewLayout的时候我觉得可以分成3个步骤:
1.重写UICollectionViewLayout的prepareLayout,在该方法里事先将计算好的一些必要的布局信息,并且存储起来;
2.基于prepareLayout方法中的布局信息,使用collectionViewContentSize方法返回UICollectionView的内容尺寸;
3.使用layoutAttributesForElementsInRect方法返回指定的区域的cell、Supplementary View和Decoration View的布局属性。
4.2 自定义UICollectionViewLayout的Demo
稍后补充Demo以及效果图
5.开发过程中遇到的问题以及解决方案
5.1 问题一:
报错信息:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'UICollectionView must be initialized with a non-nil layout parameter'
错误原因分析:
UICollectionView缺少布局对象。
解决方案:
给UICollectionView指定布局对象(UICollectionViewLayout)
5.2 问题二:
报错信息:
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)
5.3 问题三:
报错信息:
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。
5.4 问题四:
报错信息:
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样式。
5.5 问题五
报错信息:
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:如有遗漏的地方,还烦请大神指正补充。
网友评论