一、实现目标
当我们要实现App store
的游戏页面的时候,惯性思维可能就是我们需要建立一个UITableView
,并且在tableHeaderView
或者在第一个cell
内部嵌套一个横向滑动的UICollectionView
。\
其实我们可以直接用一个collectionView就可以实现这么一个效果。这就是今天的主角——UICollectionViewCompositionalLayout
。
二、层级结构
由图可见,层级关系为 NSCollectionLayoutItem -> NSCollectionLayoutGroup -> NSCollectionLayoutSection\
由图片可知,相对于原来的layout,多了一个Group属性。
三、先了解一下前期需要的配置
1.NSCollectionLayoutSize
我们先看看如何去声明一个NSCollectionLayoutSize
//1.配置NSCollectionLayoutSize
NSCollectionLayoutDimension *itemSizeWidthDimension = [NSCollectionLayoutDimension fractionalWidthDimension:0.25];
NSCollectionLayoutDimension *itemSizeHeightDimension = [NSCollectionLayoutDimension fractionalHeightDimension:1.0];
NSCollectionLayoutSize *fractionalItemSize = [NSCollectionLayoutSize sizeWithWidthDimension:itemSizeWidthDimension
heightDimension:itemSizeHeightDimension];
在这个函数中我们需要传入两个NSCollectionLayoutDimension
对象,去分别控制Item的宽和高。我们点进去可以看到
+ (instancetype)fractionalWidthDimension:(CGFloat)fractionalWidth;
+ (instancetype)fractionalHeightDimension:(CGFloat)fractionalHeight;
+ (instancetype)absoluteDimension:(CGFloat)absoluteDimension;
+ (instancetype)estimatedDimension:(CGFloat)estimatedDimension;
相对于原来的UICollectionViewFlowLayout
的定义方式。NSCollectionLayoutDimension
提供了多种声明函数来定义。
- fractionalWidthDimension && fractionalHeightDimension\
fractional
是关键词,可以理解为一个相对布局,后面传入的是一个相对于父视图的一个比例值; - absoluteDimension\
absolute
是关键词,可以理解为我们写frame的数值,你写了多少显示的就是多少; - estimatedDimension\
estimated
这个单词我们再写UITableView
的时候比较常见,顾名思义就是你可以给一个预估值;
2.NSCollectionLayoutItem
这里对象初始化函数中需要带上我们刚才生成的itemSize。
//2.配置NSCollectionLayoutItem
NSCollectionLayoutItem *item = [NSCollectionLayoutItem itemWithLayoutSize:fractionalItemSize];
3.NSCollectionLayoutGroup
一共提供了5个初始化的方法,主要分为3大类。
- 水平
- 垂直
- 自定义
其中水平和垂直方向都提供了两种初始化的方式,主要的差别是多了一个count参数。\
如果是自定义布局,需要传入一个NSCollectionLayoutGroupCustomItemProvider来决定这个 Group中Item的布局方式。通过Group可以在同一个Section中实现不同的布局方式。\
+ (instancetype)horizontalGroupWithLayoutSize:(NSCollectionLayoutSize*)layoutSize
repeatingSubitem:(NSCollectionLayoutItem*)subitem
count:(NSInteger)count;
+ (instancetype)horizontalGroupWithLayoutSize:(NSCollectionLayoutSize*)layoutSize
subitems:(NSArray<NSCollectionLayoutItem*>*)subitems;
+ (instancetype)verticalGroupWithLayoutSize:(NSCollectionLayoutSize*)layoutSize
repeatingSubitem:(NSCollectionLayoutItem*)subitem
count:(NSInteger)count;
+ (instancetype)verticalGroupWithLayoutSize:(NSCollectionLayoutSize*)layoutSize
subitems:(NSArray<NSCollectionLayoutItem*>*)subitems;
+ (instancetype)customGroupWithLayoutSize:(NSCollectionLayoutSize*)layoutSize
itemProvider:(NSCollectionLayoutGroupCustomItemProvider)itemProvider;
需要注意的是:\
这里初始化函数的第一个参数layoutSize
需要重新声明一个size对象,不能使用我们第一步的那个size。
//3.配置NSCollectionLayoutGroup
NSCollectionLayoutDimension *gropSizeWidthDimension = [NSCollectionLayoutDimension fractionalWidthDimension:1.0];
NSCollectionLayoutDimension *gropSizeHeightDimension = [NSCollectionLayoutDimension fractionalHeightDimension:0.1];
NSCollectionLayoutSize *gropSize = [NSCollectionLayoutSize sizeWithWidthDimension:gropSizeWidthDimension
heightDimension:gropSizeHeightDimension];
NSCollectionLayoutGroup *group = [NSCollectionLayoutGroup horizontalGroupWithLayoutSize:gropSize
subitems:@[item]];
4.NSCollectionLayoutSection
初始化的时候带上之前定义好的group就可以。
//4.配置NSCollectionLayoutSection
NSCollectionLayoutSection *section = [NSCollectionLayoutSection sectionWithGroup:group];
5.UICollectionViewCompositionalLayout
初始化的时候把我们之前声明好的section
带入就可以
//5.配置UICollectionViewCompositionalLayout
UICollectionViewCompositionalLayout *layout = [[UICollectionViewCompositionalLayout alloc] initWithSection:section];
但是这样的声明方式只能声明一个section。
四、探索实际的应用
1.案例一:实现一个最简单的CompositionalLayout
上一个部分已经介绍了如何去实现一个CompositionalLayout,那么我们接下来就要把我们设计的布局放到CollectionView中去实现。只要我们在CollectionView的声明函数中代入我们的Layout或者单独的用collectionView.collectionViewLayout = layout;
来实现就行。剩下的及时要去实现基本的代理方法就可以
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
//1.配置NSCollectionLayoutSize
NSCollectionLayoutDimension *itemSizeWidthDimension = [NSCollectionLayoutDimension fractionalWidthDimension:0.25];
NSCollectionLayoutDimension *itemSizeHeightDimension = [NSCollectionLayoutDimension fractionalHeightDimension:1.0];
NSCollectionLayoutSize *fractionalItemSize = [NSCollectionLayoutSize sizeWithWidthDimension:itemSizeWidthDimension
heightDimension:itemSizeHeightDimension];
//2.配置NSCollectionLayoutItem
NSCollectionLayoutItem *item = [NSCollectionLayoutItem itemWithLayoutSize:fractionalItemSize];
//3.配置NSCollectionLayoutGroup
NSCollectionLayoutDimension *gropSizeWidthDimension = [NSCollectionLayoutDimension fractionalWidthDimension:1.0];
NSCollectionLayoutDimension *gropSizeHeightDimension = [NSCollectionLayoutDimension fractionalHeightDimension:0.1];
NSCollectionLayoutSize *gropSize = [NSCollectionLayoutSize sizeWithWidthDimension:gropSizeWidthDimension
heightDimension:gropSizeHeightDimension];
NSCollectionLayoutGroup *group = [NSCollectionLayoutGroup horizontalGroupWithLayoutSize:gropSize
subitems:@[item]];
//4.配置NSCollectionLayoutSection
NSCollectionLayoutSection *section = [NSCollectionLayoutSection sectionWithGroup:group];
//5.配置UICollectionViewCompositionalLayout
UICollectionViewCompositionalLayout *layout = [[UICollectionViewCompositionalLayout alloc] initWithSection:section];
UICollectionView *collectionView = [[UICollectionView alloc]initWithFrame:self.view.frame collectionViewLayout:layout];
collectionView.delegate = self;
collectionView.dataSource = self;
[collectionView registerClass:[EazyCollectionViewCell class] forCellWithReuseIdentifier:NSStringFromClass([EazyCollectionViewCell class])];
[self.view addSubview:collectionView];
}
#pragma mark - <UICollectionViewDelegate,UICollectionViewDataSource>
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView{
return 2;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
return 8;
}
- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
EazyCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:NSStringFromClass([EazyCollectionViewCell class]) forIndexPath:indexPath];
if (indexPath.section ==0) {
cell.backgroundColor = [UIColor redColor];
} else {
cell.backgroundColor = [UIColor yellowColor];
}
cell.titleLabel.text = [NSString stringWithFormat:@"%ld---%ld",(long)indexPath.section,(long)indexPath.item];
return cell;
}
\
这就是我们实现的效果,直观的感觉是不是感觉和之前的Layout没什么区别,这么写反而更加的复杂。\
这样的原因主要是因为我们的UICollectionViewCompositionalLayout
的声明方式只绑定了一种NSCollectionLayoutSection
。
2.案例二:让UICollectionViewCompositionalLayout绑定多个section
让我们再次的点进UICollectionViewCompositionalLayout
内部,我们能看到这么一个函数
- (instancetype)initWithSectionProvider:(UICollectionViewCompositionalLayoutSectionProvider)sectionProvider;
我们可以根据block中的sectionIndex
属性来判断是哪个section
从而提前布局
UICollectionViewCompositionalLayout *layout = [[UICollectionViewCompositionalLayout alloc] initWithSectionProvider:^NSCollectionLayoutSection * _Nullable(NSInteger sectionIndex, id<NSCollectionLayoutEnvironment> _Nonnull layoutEnvironment) {
if (sectionIndex == 0) {
NSCollectionLayoutDimension *itemSizeWidthDimension = [NSCollectionLayoutDimension fractionalWidthDimension:0.25];
NSCollectionLayoutDimension *itemSizeHeightDimension = [NSCollectionLayoutDimension fractionalHeightDimension:1.0];
NSCollectionLayoutSize *fractionalItemSize = [NSCollectionLayoutSize sizeWithWidthDimension:itemSizeWidthDimension
heightDimension:itemSizeHeightDimension];
NSCollectionLayoutItem *item = [NSCollectionLayoutItem itemWithLayoutSize:fractionalItemSize];
NSCollectionLayoutDimension *gropSizeWidthDimension = [NSCollectionLayoutDimension fractionalWidthDimension:1.0];
NSCollectionLayoutDimension *gropSizeHeightDimension = [NSCollectionLayoutDimension fractionalHeightDimension:0.1];
NSCollectionLayoutSize *gropSize = [NSCollectionLayoutSize sizeWithWidthDimension:gropSizeWidthDimension
heightDimension:gropSizeHeightDimension];
NSCollectionLayoutGroup *group = [NSCollectionLayoutGroup horizontalGroupWithLayoutSize:gropSize
subitems:@[item]];
NSCollectionLayoutSection *section = [NSCollectionLayoutSection sectionWithGroup:group];
return section;
} else {
NSCollectionLayoutDimension *itemSizeWidthDimension = [NSCollectionLayoutDimension fractionalWidthDimension:0.5];
NSCollectionLayoutDimension *itemSizeHeightDimension = [NSCollectionLayoutDimension fractionalHeightDimension:1.0];
NSCollectionLayoutSize *fractionalItemSize = [NSCollectionLayoutSize sizeWithWidthDimension:itemSizeWidthDimension
heightDimension:itemSizeHeightDimension];
NSCollectionLayoutItem *item = [NSCollectionLayoutItem itemWithLayoutSize:fractionalItemSize];
NSCollectionLayoutDimension *gropSizeWidthDimension = [NSCollectionLayoutDimension fractionalWidthDimension:1.0];
NSCollectionLayoutDimension *gropSizeHeightDimension = [NSCollectionLayoutDimension fractionalHeightDimension:0.2];
NSCollectionLayoutSize *gropSize = [NSCollectionLayoutSize sizeWithWidthDimension:gropSizeWidthDimension
heightDimension:gropSizeHeightDimension];
NSCollectionLayoutGroup *group = [NSCollectionLayoutGroup horizontalGroupWithLayoutSize:gropSize
subitems:@[item]];
NSCollectionLayoutSection *section = [NSCollectionLayoutSection sectionWithGroup:group];
return section;
}
}];
3.进阶属性设置
3.1 间距
UICollectionViewFlowLayout
设置间距属性只要设置minimumLineSpacing
和minimumInteritemSpacing
就可以。\
但是在UICollectionViewCompositionalLayout
里面我暂时并没有发现这两个属性。这里设置间距主要分为三种:
- item和item之间
- group和group之间
- section和section之间
而设置间距的方式有两种方式:
- contentInsets
- spacing
3.1.1 ContentInsets
Item、Group 和 Section 都有一个属性 contentInsets 用于设置边距。
3.1.1.1 item.contentInsets
当给 Item 设置
contentInsets
后的示意图:
灰色区域是 Item,红色框是 Item 的边界,红色的上下左右边距就是设置的 contentInsets
- 设置语法
item.contentInsets = NSDirectionalEdgeInsetsMake(5, 5, 5, 5);
3.1.1.2 group.contentInsets
- 当给 Group 设置
contentInsets
后的示意图: \
灰色区域是 Item,红色框是 Item 的边界,蓝色框是 Group 的边界,蓝色的上下左右边距就是设置的 contentInsets。 - 设置语法
group.contentInsets = NSDirectionalEdgeInsetsMake(5, 5, 5, 5);
3.1.1.3 section.contentInsets
当给 Section 设置
contentInsets
后的示意图:
灰色区域是 Item,红色框是 Item 的边界,蓝色框是 Group 的边界,绿色框是 Section 的边界,绿色的上下左右边距就是设置的 contentInsets。
- 设置语法
group.contentInsets = NSDirectionalEdgeInsetsMake(5, 5, 5, 5);
注意须知
为了使整体的上下左右边距一样,通常需要同时设置 Item 和 Group 的contentInsets
。
3.2 Spacing
可以直接给 Group 和 Section 设置相应的 Spacing 以达到设置 Item 和 Group 之间间距的目的,但这种需要精确计算间距的值,因为间距会挤占 Item 和 Group 的空间。
group.interItemSpacing = [NSCollectionLayoutSpacing fixedSpacing:8];
group.edgeSpacing = [NSCollectionLayoutEdgeSpacing spacingForLeading:[NSCollectionLayoutSpacing fixedSpacing:8] top:[NSCollectionLayoutSpacing fixedSpacing:8] trailing:[NSCollectionLayoutSpacing fixedSpacing:8] bottom:[NSCollectionLayoutSpacing fixedSpacing:8]];
section.interItemSpacing = [NSCollectionLayoutSpacing fixedSpacing:8];
通过实验得知:
当我们给group设置属性的时候,作用是来实现item之间的spacing;
当给section设置属性的时候,作用是来实现group之间的spacing;
3.3 滚动方式
typedef NS_ENUM(NSInteger,UICollectionLayoutSectionOrthogonalScrollingBehavior) {
// default behavior. Section will layout along main layout axis (i.e. configuration.scrollDirection)
//默认行为。Section将沿着主布局轴(即configuration.scrollDirection)进行布局。
UICollectionLayoutSectionOrthogonalScrollingBehaviorNone,
// NOTE: For each of the remaining cases, the section content will layout orthogonal to the main layout axis (e.g. main layout axis == .vertical, section will scroll in .horizontal axis)
// Standard scroll view behavior: UIScrollViewDecelerationRateNormal
//注意:对于其余每种情况,section内容的布局将与主布局轴正交(例如,主布局轴== .垂直,section将在。水平轴上滚动)
//滚动视图的标准行为:uiscrollviewdedeerationratnormal
UICollectionLayoutSectionOrthogonalScrollingBehaviorContinuous,
// Scrolling will come to rest on the leading edge of a group boundary
//滚动将停在组边界的前缘
UICollectionLayoutSectionOrthogonalScrollingBehaviorContinuousGroupLeadingBoundary,
// Standard scroll view paging behavior (UIScrollViewDecelerationRateFast) with page size == extent of the collection view's bounds
//标准滚动视图分页行为(uiscrollviewdedeerationratefast),页面大小==集合视图边界的范围
UICollectionLayoutSectionOrthogonalScrollingBehaviorPaging,
// Fractional size paging behavior determined by the sections layout group's dimension
//分段大小的分页行为由section布局组的维度决定
UICollectionLayoutSectionOrthogonalScrollingBehaviorGroupPaging,
// Same of group paging with additional leading and trailing content insets to center each group's contents along the orthogonal axis
//与组分页相同,添加了额外的前导和尾内容插入,以使每个组的内容沿正交轴居中
UICollectionLayoutSectionOrthogonalScrollingBehaviorGroupPagingCentered,
}
系统给我们提供了以上6种滚动方式,我们通过绑定多组不同的section的同时也可以给每个section设置不同的滚动方式,这样就可以实现我们这边文章的主题,\
用一个布局来解决嵌套问题—— UICollectionViewCompositionalLayout
五、鸣谢
这次文章的撰写,我在网上也借鉴了很多优秀博主的文章:
这两篇文章或者Demo中使用的都是Swift语言,虽然现在OC渐渐地已经不再那么火热,但是基于工作原因,使用的还是OC所以就萌生了写这么一篇文章的想法。也希望能在这个平台多和各位技术大牛们交流学习。