当前位置: 代码迷 >> 综合 >> iOS-UICollectionView 自定义瀑布流无法添加SectionHeaderView的问题解决
  详细解决方案

iOS-UICollectionView 自定义瀑布流无法添加SectionHeaderView的问题解决

热度:38   发布时间:2023-12-26 01:54:36.0

iOS-UICollectionView 自定义瀑布流无法添加SectionHeaderView的问题解决

  • 前言
  • 自定义瀑布流
    • 瀑布流创建
    • 问题原因分析
  • 代码修改
      • 1.添加一个headerReferenceSize属性
      • 2.在初始化中设置数值
      • 3.添加头部视图布局到Section布局中
      • 4.计算collectionView的contentSize
      • 5.让每个Item向下偏移
  • collectionView的创建
  • 总结
  • 参考文献

前言

最近在项目中需要自定义一个UICollectionView瀑布流并且为Section添加一个HeaderView。但是以平时的方法设置之后并没有实现,甚至代理方法都没有执行,在查阅资料后得到了解决方法,在此记录一下。

自定义瀑布流

瀑布流创建

首先,我们得创建一个自定义的瀑布流,或者从网上搜一个,但是搜索到的好几个都出现了Cell数量不符的问题,这里我找到了一个数量正确且轻量好用的瀑布流:XRWaterfallLayout ,在下载下来使用之后,实现了我想要的瀑布流效果,但是在添加SectionHeaderView的时候却无法添加,所以我对其进行了改动。

问题原因分析

UIKit提供的瀑布流 UICollectionViewFlowLayout包含了一个设置表头的属性 headerReferenceSize 而自定义的瀑布流是继承自UICollectionViewLayout就不包含这个属性。且自定义瀑布流的布局中没有为表头提供位置。这两个原因导致了对表头的设置无效,就算执行了代理方法也不会显示到屏幕上。知道原因之后就能对症下药了!

代码修改

1.添加一个headerReferenceSize属性

#pragma mark - 属性
//添加一个表头的size
@property (nonatomic, assign) CGSize headerReferenceSize;//总共多少列,默认是2
@property (nonatomic, assign) NSInteger columnCount;

2.在初始化中设置数值

在初始化中设置数值,这里设置成了 CGSizeZero 可以根据自己的情况而定。对于不同的 Section 需要设置不同高度的情况,可以参考下面的原生方法,自定义一个代理来实现。

@protocol UICollectionViewDelegateFlowLayout <UICollectionViewDelegate>
@optional
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section;

我的需求只有一个Section,所以我就直接设置了

#pragma mark- 构造方法
- (instancetype)init {
    if (self = [super init]) {
    self.columnCount = 2;self.headerReferenceSize = CGSizeZero;}return self;
}- (instancetype)initWithColumnCount:(NSInteger)columnCount {
    if (self = [super init]) {
    self.columnCount = columnCount;self.headerReferenceSize = CGSizeZero;}return self;
}

3.添加头部视图布局到Section布局中

//布局前的准备工作
- (void)prepareLayout {
    [super prepareLayout];//初始化字典,有几列就有几个键值对,key为列,value为列的最大y值,初始值为上内边距for (int i = 0; i < self.columnCount; i++) {
    self.maxYDic[@(i)] = @(self.sectionInset.top);}//根据collectionView获取总共有多少个itemNSInteger itemCount = [self.collectionView numberOfItemsInSection:0];[self.attributesArray removeAllObjects];//添加头部视图布局UICollectionViewLayoutAttributes * layoutHeader = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader withIndexPath:[NSIndexPath indexPathWithIndex:0]];layoutHeader.frame = CGRectMake(0, 0, self.headerReferenceSize.width, self.headerReferenceSize.height);[self.attributesArray addObject:layoutHeader];//为每一个item创建一个attributes并存入数组for (int i = 0; i < itemCount; i++) {
    UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:i inSection:0]];[self.attributesArray addObject:attributes];}
}

4.计算collectionView的contentSize

计算collectionView的contentSize中也要加上headerReferenceSize.height

//计算collectionView的contentSize
- (CGSize)collectionViewContentSize {
    __block NSNumber *maxIndex = @0;//遍历字典,找出最长的那一列[self.maxYDic enumerateKeysAndObjectsUsingBlock:^(NSNumber *key, NSNumber *obj, BOOL *stop) {
    if ([self.maxYDic[maxIndex] floatValue] < obj.floatValue) {
    maxIndex = key;}}];//collectionView的contentSize.height就等于最长列的最大y值+下内边距+头部视图高return CGSizeMake(0, [self.maxYDic[maxIndex] floatValue] + self.sectionInset.bottom + self.headerReferenceSize.height);
}

5.让每个Item向下偏移

- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
    //根据indexPath获取item的attributesUICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];//获取collectionView的宽度CGFloat collectionViewWidth = self.collectionView.frame.size.width;//item的宽度 = (collectionView的宽度 - 内边距与列间距) / 列数CGFloat itemWidth = (collectionViewWidth - self.sectionInset.left - self.sectionInset.right - (self.columnCount - 1) * self.columnSpacing) / self.columnCount;CGFloat itemHeight = 0;//获取item的高度,由外界计算得到if (self.itemHeightBlock) itemHeight = self.itemHeightBlock(itemWidth, indexPath);else {
    if ([self.delegate respondsToSelector:@selector(waterfallLayout:itemHeightForWidth:atIndexPath:)])itemHeight = [self.delegate waterfallLayout:self itemHeightForWidth:itemWidth atIndexPath:indexPath];}//找出最短的那一列__block NSNumber *minIndex = @0;[self.maxYDic enumerateKeysAndObjectsUsingBlock:^(NSNumber *key, NSNumber *obj, BOOL *stop) {
    if ([self.maxYDic[minIndex] floatValue] > obj.floatValue) {
    minIndex = key;}}];//根据最短列的列数计算item的x值CGFloat itemX = self.sectionInset.left + (self.columnSpacing + itemWidth) * minIndex.integerValue;//item的y值 = 最短列的最大y值 + 行间距CGFloat itemY = [self.maxYDic[minIndex] floatValue] + self.rowSpacing;//更新字典中的最大y值self.maxYDic[minIndex] = @(itemY + itemHeight);//设置attributes的frameattributes.frame = CGRectMake(itemX, itemY + self.headerReferenceSize.height, itemWidth, itemHeight);return attributes;
}

注意:在原先的代码里,更新字典中的最大Y值是这样写的

    //设置attributes的frameattributes.frame = CGRectMake(itemX, itemY, itemWidth, itemHeight);//更新字典中的最大y值self.maxYDic[minIndex] = @(CGRectGetMaxY(attributes.frame));

如果直接将headerReferenceSize.height 加到attributesframe中,每一个item的Y都会加上这个高度,间隙就会变大。正确的添加方法是:将更新最大值放到设置attributesframe前面。

    //更新字典中的最大y值self.maxYDic[minIndex] = @(itemY + itemHeight);//设置attributes的frameattributes.frame = CGRectMake(itemX, itemY + self.headerReferenceSize.height, itemWidth, itemHeight);

collectionView的创建

对于collectionView的创建,就需要将布局换成自定义的瀑布流即可

#pragma mark - Lazy
- (UICollectionView *)collectionView {
    if (!_collectionView) {
    XRWaterfallLayout *waterfall = [XRWaterfallLayout waterFallLayoutWithColumnCount:2];waterfall.delegate = self;// 在这里设置头部试图的尺寸waterfall.headerReferenceSize = CGSizeMake(SCREEN_WIDTH, 30);[waterfall setColumnSpacing:10 rowSpacing:10 sectionInset:UIEdgeInsetsMake(10, 10, 10, 10)];_collectionView = [[UICollectionView alloc] initWithFrame:UniformFrame collectionViewLayout:waterfall];_collectionView.delegate = self;_collectionView.dataSource = self;_collectionView.backgroundColor = UIColor.whiteColor;_collectionView.alwaysBounceVertical = YES;[_collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:NewHomeVCCellIdentifier];// 必须有!!![_collectionView registerClass:[NewHomeSectionHeaderView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:NewHomeVCSectionHeaderIdentifier];}return _collectionView;
}#pragma mark - UICollectionViewDelegate & UICollectionViewDataSource
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {
    if(kind == UICollectionElementKindSectionHeader){
    NewHomeSectionHeaderView *headerView = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:NewHomeVCSectionHeaderIdentifier forIndexPath:indexPath];return headerView;} else {
    // 注意,在设置了footerView的情况下是不能返回nil的,不然会崩溃!return nil;}
}

总结

这个问题考察的是程序员对于瀑布流布局的熟悉程度,其中涉及到了瀑布流的具体布局、UICollectionViewdelegate&dataSource方法实现、UICollectionElementKindSectionHeader的设置方法等知识点。也要告诫自己,出现问题要冷静分析、断点查看流程、剖析问题原因、和同事沟通、查找资料、从根本上解决问题,不能急躁。

参考文献

《iOS之简单瀑布流的实现》
《iOS - UICollectionView 瀑布流 添加表头视图的坑》
《iOS tableView、collectionView添加SectionHeader》
《iOS开发之UICollectionViewController系列(二) :详解CollectionView各种回调》

  相关解决方案