一、完成目标

iOS 用一个布局来解决嵌套问题—— UICollectionViewCompositionalLayout
当咱们要完成App store的游戏页面的时分,惯性思想或许便是咱们需求树立一个UITableView,并且在tableHeaderView或许在第一个cell内部嵌套一个横向滑动的UICollectionView
其实咱们能够直接用一个collectionView就能够完成这么一个作用。这便是今天的主角——UICollectionViewCompositionalLayout

二、层级结构

iOS 用一个布局来解决嵌套问题—— 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;
}

iOS 用一个布局来解决嵌套问题—— UICollectionViewCompositionalLayout

这便是咱们完成的作用,直观的感觉是不是感觉和之前的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;
    }
  }];

iOS 用一个布局来解决嵌套问题—— UICollectionViewCompositionalLayout

3.进阶特点设置

3.1 距离

UICollectionViewFlowLayout设置距离特点只需设置minimumLineSpacingminimumInteritemSpacing就能够。
但是在UICollectionViewCompositionalLayout里面我暂时并没有发现这两个特点。这儿设置距离主要分为三种:

  1. item和item之间
  2. group和group之间
  3. section和section之间

而设置距离的办法有两种办法:

  1. contentInsets
  2. spacing

3.1.1 ContentInsets

Item、Group 和 Section 都有一个特点 contentInsets 用于设置边距。

3.1.1.1 item.contentInsets
  • 当给 Item 设置contentInsets后的示意图
iOS 用一个布局来解决嵌套问题—— UICollectionViewCompositionalLayout

灰色区域是 Item,赤色框是 Item 的鸿沟,赤色的上下左右边距便是设置的 contentInsets

  • 设置语法
item.contentInsets = NSDirectionalEdgeInsetsMake(5, 5, 5, 5);
3.1.1.2 group.contentInsets
  • 当给 Group 设置contentInsets后的示意图:
    iOS 用一个布局来解决嵌套问题—— UICollectionViewCompositionalLayout

    灰色区域是 Item,赤色框是 Item 的鸿沟,蓝色框是 Group 的鸿沟,蓝色的上下左右边距便是设置的 contentInsets。
  • 设置语法
group.contentInsets = NSDirectionalEdgeInsetsMake(5, 5, 5, 5);
3.1.1.3 section.contentInsets
  • 当给 Section 设置contentInsets后的示意图:

    iOS 用一个布局来解决嵌套问题—— UICollectionViewCompositionalLayout

    灰色区域是 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

五、道谢

这次文章的撰写,我在网上也借鉴了很多优秀博主的文章:

  1. iOS开发之UICollectionViewCompositionalLayout
  2. CompositionalLayout_Demo

这两篇文章或许Demo中运用的都是Swift言语,虽然现在OC渐渐地现已不再那么火热,但是根据作业原因,运用的还是OC所以就萌生了写这么一篇文章的想法。也希望能在这个渠道多和各位技术大牛们交流学习。