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

前言

最近在项目中需要自定义一个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获取总共有多少个item
    NSInteger 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的attributes
    UICollectionViewLayoutAttributes *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的frame
    attributes.frame = CGRectMake(itemX, itemY + self.headerReferenceSize.height, itemWidth, itemHeight);
    
    return attributes;
}

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

    //设置attributes的frame
    attributes.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的frame
    attributes.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各种回调》

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值