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 加到attributes的frame中,每一个item的Y都会加上这个高度,间隙就会变大。正确的添加方法是:将更新最大值放到设置attributes的frame前面。
//更新字典中的最大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;}
}
总结
这个问题考察的是程序员对于瀑布流布局的熟悉程度,其中涉及到了瀑布流的具体布局、UICollectionView的delegate&dataSource方法实现、UICollectionElementKindSectionHeader的设置方法等知识点。也要告诫自己,出现问题要冷静分析、断点查看流程、剖析问题原因、和同事沟通、查找资料、从根本上解决问题,不能急躁。
参考文献
《iOS之简单瀑布流的实现》
《iOS - UICollectionView 瀑布流 添加表头视图的坑》
《iOS tableView、collectionView添加SectionHeader》
《iOS开发之UICollectionViewController系列(二) :详解CollectionView各种回调》