前言
众所周知,UICollectionView
是一个非常灵活的控件,可以实现各种酷炫的样式,究其原因,是因为苹果巧妙地将UICollectionView
的布局和渲染分隔开了,UICollectionView
负责渲染,而UICollectionViewLayout
负责布局。但是UICollectionViewLayout
是一个抽象类,我们不能直接使用,需要定义他的子类来进行布局。
iOS 13之前
流式布局
在iOS 13之前,苹果为我们实现了一个具体的布局类UICollectionViewFlowLayout
,它是UICollectionViewLayout
的子类,通过类名就能看出,这个布局类实现的是流式布局,即按照“行”进行布局。举个例子,假如你的UICollectionView
是垂直滚动的,那么这条“行”就是水平方向的,UICollectionView
的item沿着水平方向进行填充,直到水平方向放不下则换到下一行继续沿着水平方向填充,结果如下图所示,当然,如果你的滚动方向是水平方向,那么这个“行”就是沿着垂直方向。如果你之前用过UICollectionView
的话,那么对Flow布局肯定不陌生。
自定义布局
UICollectionViewFlowLayout
对于一些简单的布局还是很有用的,但是遇到复杂的布局就显得力不从心了,那么这个时候我们就需要自定义布局类进行操作,自定义布局类需要重写4个方法:
// 布局前的准备工作
- (void)prepareLayout
/**
设置CollectionView的滚动区域
layoutAttributesForElementsInRect方法调用之后都会调用该方法
*/
- (CGSize)collectionViewContentSize
/**
返回指定区域所有cell的布局属性
这个方法会多次执行,直到所有cell都显示出来就不执行了
*/
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
// 返回IndexPath位置的item的布局属性
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
本篇文章主要讲解iOS 13新的布局方式,而且代码中都有注释,这里就不具体讲解了。
虽然UICollectionViewLayout
能实现各种灵活的效果,但是他也有自己的局限性——滚动方向只有一个,即不论你怎么自定义布局类,一个UICollectionView
只能有一个滚动方向。
那如果我要实现UICollectionView
不同section有不同的滚动方向要怎么做呢?iOS 13之前,你只能在UICollectionView
中嵌套UICollectionView
来实现,但是iOS 13之后就不需要了,因为iOS 13苹果为我们带来了一个全新的布局类——UICollectionViewCompositionalLayout
iOS 13
基本概念
UICollectionViewCompositionalLayout
是苹果在iOS 13推出的全新的布局类,该类不再以子类化的方式定义布局,而是以组合的形式,因此该布局方式有3个特点:
- 组合
- 灵活
- 快速
UICollectionViewCompositionalLayout
布局类主要包含4个部分:item
、group
、section
和layout
,它们之间的关系如下图所示:
-
item
:这个不用过多解释,指的就是我们UICollectionView
的每个cell和supplement view。 -
group
:这个是UICollectionViewCompositionalLayout
新加的概念,group
类似于流式布局,也是基于“行”进行的,可以是水平方向,也可以是垂直方向,group
还可以嵌套group
,这样就可以实现局部样式的多样化。 -
section
:等同于UICollectionView
的section。 -
layout
:整个UICollectionView
的布局。
核心类
NSCollectionLayoutSize
NSCollectionLayoutItem
NSCollectionLayoutGroup
NSCollectionLayoutSection
UICollectionViewCompositionalLayout
第2~5分别对应上面4个部分,后面的代码会有介绍如何使用,这里重点介绍一下NSCollectionLayoutSize
,这是苹果新推出的一个类,用来计算布局尺寸,它提供了一个类方法+ (instancetype)sizeWithWidthDimension:(NSCollectionLayoutDimension*)width heightDimension:(NSCollectionLayoutDimension*)height;
,通过传入width和height,就能计算出相应的尺寸,需要注意的是,这里的width和height不是我们之前定义frame时的宽高,而是一个NSCollectionLayoutDimension
的实例,NSCollectionLayoutDimension
也是苹果新推出的一个类,用来定义尺寸大小,它提供了3种定义尺寸的方式——fractional
、absolute
和estimated
,对应的方法如下:
// dimension is computed as a fraction of the width of the containing group
+ (instancetype)fractionalWidthDimension:(CGFloat)fractionalWidth;
// dimension is computed as a fraction of the height of the containing group
+ (instancetype)fractionalHeightDimension:(CGFloat)fractionalHeight;
// dimension with an absolute point value
+ (instancetype)absoluteDimension:(CGFloat)absoluteDimension;
// dimension is estimated with a point value. Actual size will be determined when the content is rendered.
+ (instancetype)estimatedDimension:(CGFloat)estimatedDimension;
fractional
是一个浮点数,表示视图的宽高占父视图宽高的百分比,值可以大于1。下图定义了一个高度是父视图高度30%的视图。
absolute
是一个绝对的数值,表示一个具体的尺寸,跟frame的定义一样,单位是point。下图定义了一个高度是200个point的视图。
estimated
是一个估算值,表示实际渲染的视图宽高可能变化,在self-sizing
中使用。下图定义了一个估算值是200,实际高度是257的视图。
使用
理论知识讲的差不多了,下面就来实践一下吧。下面我们实现一个类似UITableView
的简单列表,效果图如下:
该列表的布局代码如下:
- (UICollectionViewLayout *)generateLayout {
// 1.item
NSCollectionLayoutSize *itemSize = [NSCollectionLayoutSize sizeWithWidthDimension:[NSCollectionLayoutDimension fractionalWidthDimension:1.0] heightDimension:[NSCollectionLayoutDimension fractionalHeightDimension:1.0]];
NSCollectionLayoutItem *item = [NSCollectionLayoutItem itemWithLayoutSize:itemSize];
// 2.group
NSCollectionLayoutSize *groupSize = [NSCollectionLayoutSize sizeWithWidthDimension:[NSCollectionLayoutDimension fractionalWidthDimension:1.0] heightDimension:[NSCollectionLayoutDimension absoluteDimension:44]];
NSCollectionLayoutGroup *group = [NSCollectionLayoutGroup horizontalGroupWithLayoutSize:groupSize subitem:item count:1];
// 3.section
NSCollectionLayoutSection *section = [NSCollectionLayoutSection sectionWithGroup:group];
// 4.layout
return [[UICollectionViewCompositionalLayout alloc] initWithSection:section];
}
按照之前讲的理论知识,定义一个布局分4步:
- 定义一个
item
,宽高占父视图(group
)的100%,即宽高等于父视图。 - 定义一个
group
,宽度占父视图的100%,高度等于44个point,它包含一个item
。 - 定义一个
section
,将group
包含进来。 - 用
section
定义一个layout。
通过上面4步就完成了简单列表的布局。
总结
本篇文章介绍了iOS 13之前如何定义布局,以及iOS 13新推出的布局方式,用iOS 13新的布局方式实现了一个类似UITableView
的简单列表,该列表虽然简单,但是包含了新布局方式的实现过程,复杂视图也只是上面步骤的变种,如果想查看其它样式的Demo,请点击链接查看,如果有写的不对的地方请指正,欢迎大家交流讨论,谢谢大家!