实现UICollectionView悬浮section header需要自定义flowlayout,我们继承自UICollectionViewFlowLayout
DMHeaderFlowLayout.h文件如下
@interface DMHeaderFlowLayout : UICollectionViewFlowLayout
@property (nonatomic, assign) CGFloat navHeight;
@end
DMHeaderFlowLayout.m如下
#import "DMHeaderFlowLayout.h"
@interface DMHeaderFlowLayout ()
@end
@implementation DMHeaderFlowLayout
#pragma mark - 初始化
- (instancetype)init
{
if (self = [super init]) {
_navHeight = 0.0;
}
return self;
}
/**
* 作用:返回指定区域的item(包括cell和header、footer这些)布局对象
*/
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
//截取到父类所返回的数组(里面放的是当前屏幕所能展示的item的结构信息),并转化成可变数组
NSMutableArray *superArray = [[super layoutAttributesForElementsInRect:rect] mutableCopy];
//创建存索引的数组,无符号(正整数),无序(不能通过下标取值),不可重复(重复的话会自动过滤)
NSMutableIndexSet *noneHeaderSections = [NSMutableIndexSet indexSet];
//遍历superArray,得到一个当前屏幕中所有的section数组
for (UICollectionViewLayoutAttributes *attributes in superArray) {
//如果当前的元素分类是一个cell,将cell所在的分区section加入数组,重复的话会自动过滤
if (attributes.representedElementCategory == UICollectionElementCategoryCell) {
[noneHeaderSections addIndex:attributes.indexPath.section];
}
}
//遍历superArray,将当前屏幕中拥有的header的section从数组中移除,得到一个当前屏幕中没有header的section数组
//正常情况下,随着手指往上移,header脱离屏幕会被系统回收而cell尚在,也会触发该方法
for (UICollectionViewLayoutAttributes *attributes in superArray) {
//如果当前的元素是一个header,将header所在的section从数组中移除
if ([attributes.representedElementKind isEqualToString:UICollectionElementKindSectionHeader]) {
[noneHeaderSections removeIndex:attributes.indexPath.section];
}
}
//遍历当前屏幕中没有header的section数组
[noneHeaderSections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
CGFloat HeaderHeight = self.headerReferenceSize.height;
id<UICollectionViewDelegateFlowLayout> tmpDelegate = (id<UICollectionViewDelegateFlowLayout>)self.collectionView.delegate;
if ([tmpDelegate respondsToSelector:@selector(collectionView:layout:referenceSizeForHeaderInSection:)]) {
CGSize headerSize = [tmpDelegate collectionView:self.collectionView layout:self referenceSizeForHeaderInSection:idx];
HeaderHeight = headerSize.height;
}
if (HeaderHeight > 0) {
//取到当前section中第一个item的indexPath
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:idx];
//获取当前section在正常情况下已经离开屏幕的header结构信息
UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader atIndexPath:indexPath];
//如果当前分区确实有因为离开屏幕而被系统回收的header
if (attributes) {
//将该header结构信息重新加入到superArray中去
[superArray addObject:attributes];
}
}
}];
//遍历superArray,改变header结构信息中的参数,使它可以在当前section还没完全离开屏幕的时候一直显示
for (UICollectionViewLayoutAttributes *attributes in superArray) {
//如果当前item是header
if ([attributes.representedElementKind isEqualToString:UICollectionElementKindSectionHeader]) {
//得到当前section的edgeInsets
id<UICollectionViewDelegateFlowLayout> tmpDelegate = (id<UICollectionViewDelegateFlowLayout>)self.collectionView.delegate;
UIEdgeInsets curEdgeInsets = self.sectionInset;
if ([tmpDelegate respondsToSelector:@selector(collectionView:layout:insetForSectionAtIndex:)]) {
curEdgeInsets = [tmpDelegate collectionView:self.collectionView layout:self insetForSectionAtIndex:attributes.indexPath.section];
}
//得到当前header所在分区的cell的数量
NSInteger numberOfItemsInSection = [self.collectionView numberOfItemsInSection:attributes.indexPath.section];
//得到第一个item的indexPath
NSIndexPath *firstItemIndexPath = [NSIndexPath indexPathForItem:0 inSection:attributes.indexPath.section];
//得到最后一个item的indexPath
NSIndexPath *lastItemIndexPath = [NSIndexPath indexPathForItem:MAX(0, numberOfItemsInSection-1) inSection:attributes.indexPath.section];
//得到第一个item和最后一个item的结构信息
UICollectionViewLayoutAttributes *firstItemAttributes, *lastItemAttributes;
if (numberOfItemsInSection > 0) {
//cell有值,则获取第一个cell和最后一个cell的结构信息
firstItemAttributes = [self layoutAttributesForItemAtIndexPath:firstItemIndexPath];
lastItemAttributes = [self layoutAttributesForItemAtIndexPath:lastItemIndexPath];
} else {
//cell没值,就新建一个UICollectionViewLayoutAttributes
firstItemAttributes = [UICollectionViewLayoutAttributes new];
//然后模拟出在当前分区中的唯一一个cell,cell在header的下面,高度为0,还与header隔着可能存在的sectionInset的top
CGFloat y = CGRectGetMaxY(attributes.frame) + self.sectionInset.top;
firstItemAttributes.frame = CGRectMake(0, y, 0, 0);
//因为只有一个cell,所以最后一个cell等于第一个cell
lastItemAttributes = firstItemAttributes;
}
//获取当前header的frame
CGRect rect = attributes.frame;
//当前的滑动距离 + 因为导航栏产生的偏移量,默认为0(如果app需求不同,需自己设置)
CGFloat offset = self.collectionView.contentOffset.y + self.navHeight;
//第一个cell的y值 - 当前header的高度 - 可能存在的sectionInset的top
CGFloat headerY = firstItemAttributes.frame.origin.y - rect.size.height - curEdgeInsets.top;
//哪个大取哪个,保证header悬停
//针对当前header基本上都是offset更加大,针对下一个header则会是headerY大,各自处理
CGFloat maxY = MAX(offset, headerY);
// 取得当前section的footer高度
CGFloat sectionFooterHegiht = 0;
if ([tmpDelegate respondsToSelector:@selector(collectionView:layout:referenceSizeForFooterInSection:)]) {
CGSize tmpSize = [tmpDelegate collectionView:self.collectionView layout:self referenceSizeForFooterInSection:attributes.indexPath.section];
sectionFooterHegiht = tmpSize.height;
}
//最后一个cell的y值 + 最后一个cell的高度 + 可能存在的sectionInset的bottom + section的footer高度 - 当前header的高度
//当当前section的footer或者下一个section的header接触到当前header的底部,计算出的headerMissingY即为有效值
CGFloat headerMissingY = CGRectGetMaxY(lastItemAttributes.frame) + curEdgeInsets.bottom + sectionFooterHegiht - rect.size.height;
//给rect的y赋新值,因为在最后消失的临界点要跟谁消失,所以取小
rect.origin.y = MIN(maxY,headerMissingY);
//给header的结构信息的frame重新赋值
attributes.frame = rect;
//设置attributes的层次关系zIndex数值
attributes.zIndex = 6;
}
}
//转换回不可变数组,并返回
return [superArray copy];
}
//return YES;表示一旦滑动就实时调用上面这个layoutAttributesForElementsInRect:方法
- (BOOL) shouldInvalidateLayoutForBoundsChange:(CGRect)newBound
{
return YES;
}
@end
使用代码如下
#import "DMFunctionHeaderFlowViewController.h"
#import "DMSectionHeaderCollectionReusableView.h"
#import "DMNormalCollectionViewCell.h"
#import "DMHeaderFlowLayout.h"
static NSString * const kDMSectionHeaderCollectionReusableViewHeaderFlowIdentifier = @"kDMSectionHeaderCollectionReusableViewHeaderFlowIdentifier";
static NSString * const kDMSectionHeaderCollectionReusableViewFooterFlowIdentifier = @"kDMSectionHeaderCollectionReusableViewFooterFlowIdentifier";
static NSString * const kDMNormalCollectionViewCellHeaderFlowIdentifier = @"kDMNormalCollectionViewCellHeaderFlowIdentifier";
@interface DMFunctionHeaderFlowViewController ()<UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout>
/** UI */
@property (nonatomic, strong) UICollectionView *myCollectionView;
/** DATA */
@property (nonatomic, strong) NSMutableArray *dataArray;
@end
@implementation DMFunctionHeaderFlowViewController
#pragma mark - life cycle
- (void)viewDidLoad
{
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
self.title = @"UICollectionView悬浮Section Header";
[self initData];
[self.view addSubview:self.myCollectionView];
}
- (void)initData
{
{
NSMutableArray *sectionOneArray = [[NSMutableArray alloc] init];
{
DMNormalCollectionViewCellModel *modelOne = [[DMNormalCollectionViewCellModel alloc] init];
modelOne.imageName = @"picOne";
[sectionOneArray addObject:modelOne];
}
{
DMNormalCollectionViewCellModel *modelTwo = [[DMNormalCollectionViewCellModel alloc] init];
modelTwo.imageName = @"picTow";
[sectionOneArray addObject:modelTwo];
}
{
DMNormalCollectionViewCellModel *modelThree = [[DMNormalCollectionViewCellModel alloc] init];
modelThree.imageName = @"picThree";
[sectionOneArray addObject:modelThree];
}
{
DMNormalCollectionViewCellModel *modelFour = [[DMNormalCollectionViewCellModel alloc] init];
modelFour.imageName = @"picFour";
[sectionOneArray addObject:modelFour];
}
[self.dataArray addObject:sectionOneArray];
}
{
NSMutableArray *sectionTwoArray = [[NSMutableArray alloc] init];
{
DMNormalCollectionViewCellModel *modelOne = [[DMNormalCollectionViewCellModel alloc] init];
modelOne.imageName = @"YY1";
[sectionTwoArray addObject:modelOne];
}
{
DMNormalCollectionViewCellModel *modelTwo = [[DMNormalCollectionViewCellModel alloc] init];
modelTwo.imageName = @"YY2";
[sectionTwoArray addObject:modelTwo];
}
{
DMNormalCollectionViewCellModel *modelThree = [[DMNormalCollectionViewCellModel alloc] init];
modelThree.imageName = @"YY3";
[sectionTwoArray addObject:modelThree];
}
{
DMNormalCollectionViewCellModel *modelFour = [[DMNormalCollectionViewCellModel alloc] init];
modelFour.imageName = @"YY4";
[sectionTwoArray addObject:modelFour];
}
{
DMNormalCollectionViewCellModel *modelThree = [[DMNormalCollectionViewCellModel alloc] init];
modelThree.imageName = @"YY5";
[sectionTwoArray addObject:modelThree];
}
{
DMNormalCollectionViewCellModel *modelThree = [[DMNormalCollectionViewCellModel alloc] init];
modelThree.imageName = @"YY6";
[sectionTwoArray addObject:modelThree];
}
{
DMNormalCollectionViewCellModel *modelThree = [[DMNormalCollectionViewCellModel alloc] init];
modelThree.imageName = @"YY7";
[sectionTwoArray addObject:modelThree];
}
{
DMNormalCollectionViewCellModel *modelThree = [[DMNormalCollectionViewCellModel alloc] init];
modelThree.imageName = @"YY8";
[sectionTwoArray addObject:modelThree];
}
{
DMNormalCollectionViewCellModel *modelThree = [[DMNormalCollectionViewCellModel alloc] init];
modelThree.imageName = @"YY9";
[sectionTwoArray addObject:modelThree];
}
{
DMNormalCollectionViewCellModel *modelThree = [[DMNormalCollectionViewCellModel alloc] init];
modelThree.imageName = @"YY10";
[sectionTwoArray addObject:modelThree];
}
{
DMNormalCollectionViewCellModel *modelThree = [[DMNormalCollectionViewCellModel alloc] init];
modelThree.imageName = @"YY11";
[sectionTwoArray addObject:modelThree];
}
[self.dataArray addObject:sectionTwoArray];
}
{
NSMutableArray *sectionThreeArray = [[NSMutableArray alloc] init];
{
DMNormalCollectionViewCellModel *modelOne = [[DMNormalCollectionViewCellModel alloc] init];
modelOne.imageName = @"mountain1";
[sectionThreeArray addObject:modelOne];
}
{
DMNormalCollectionViewCellModel *modelTwo = [[DMNormalCollectionViewCellModel alloc] init];
modelTwo.imageName = @"mountain2";
[sectionThreeArray addObject:modelTwo];
}
{
DMNormalCollectionViewCellModel *modelThree = [[DMNormalCollectionViewCellModel alloc] init];
modelThree.imageName = @"mountain3";
[sectionThreeArray addObject:modelThree];
}
{
DMNormalCollectionViewCellModel *modelFour = [[DMNormalCollectionViewCellModel alloc] init];
modelFour.imageName = @"mountain4";
[sectionThreeArray addObject:modelFour];
}
{
DMNormalCollectionViewCellModel *modelFour = [[DMNormalCollectionViewCellModel alloc] init];
modelFour.imageName = @"mountain5";
[sectionThreeArray addObject:modelFour];
}
{
DMNormalCollectionViewCellModel *modelFour = [[DMNormalCollectionViewCellModel alloc] init];
modelFour.imageName = @"mountain6";
[sectionThreeArray addObject:modelFour];
}
{
DMNormalCollectionViewCellModel *modelFour = [[DMNormalCollectionViewCellModel alloc] init];
modelFour.imageName = @"mountain7";
[sectionThreeArray addObject:modelFour];
}
[self.dataArray addObject:sectionThreeArray];
}
}
#pragma mark - UICollectionViewDataSource method
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
NSInteger sectionNum = 0;
sectionNum = [self.dataArray count];
return sectionNum;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
NSInteger itemNum = 0;
NSArray *itemsArray = [self.dataArray objectAtIndex:section];
itemNum = [itemsArray count];
return itemNum;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
DMNormalCollectionViewCell * tmpCell = [collectionView dequeueReusableCellWithReuseIdentifier:kDMNormalCollectionViewCellHeaderFlowIdentifier forIndexPath:indexPath];
NSArray *tmpDataArray = [self.dataArray objectAtIndex:indexPath.section];
DMNormalCollectionViewCellModel *dataModel = [tmpDataArray objectAtIndex:indexPath.row];
[tmpCell setDataModel:dataModel];
return tmpCell;
}
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{
UICollectionReusableView *tmpView = nil;
if (kind == UICollectionElementKindSectionHeader) {
if (indexPath.section != 2) {
DMSectionHeaderCollectionReusableView *headerView = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:kDMSectionHeaderCollectionReusableViewHeaderFlowIdentifier forIndexPath:indexPath];
NSString *titleStr = [NSString stringWithFormat:@"第%@个分区头部", [NSNumber numberWithInteger:indexPath.section]];
[headerView updateStr:titleStr];
headerView.backgroundColor = [UIColor greenColor];
tmpView = headerView;
}
} else if (kind == UICollectionElementKindSectionFooter) {
DMSectionHeaderCollectionReusableView *headerView = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:kDMSectionHeaderCollectionReusableViewFooterFlowIdentifier forIndexPath:indexPath];
NSString *titleStr = [NSString stringWithFormat:@"第%@个分区尾部", [NSNumber numberWithInteger:indexPath.section]];
[headerView updateStr:titleStr];
headerView.backgroundColor = [UIColor yellowColor];
tmpView = headerView;
}
return tmpView;
}
#pragma mark - UICollectionViewDelegate method
#pragma mark - UICollectionViewDelegateFlowLayout method
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
CGSize itemSize = CGSizeMake([UIScreen mainScreen].bounds.size.width, [DMNormalCollectionViewCell cellHeight]);
return itemSize;
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section
{
CGSize sectionHeaderSize = CGSizeZero;
if (section != 2) {
sectionHeaderSize = CGSizeMake([UIScreen mainScreen].bounds.size.width, 60);
}
return sectionHeaderSize;
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section
{
CGSize sectionFooterSize = CGSizeZero;
sectionFooterSize = CGSizeMake([UIScreen mainScreen].bounds.size.width, 60);
return sectionFooterSize;
}
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section
{
UIEdgeInsets sectionEdgeInsets = UIEdgeInsetsZero;
sectionEdgeInsets = UIEdgeInsetsMake(10, 0, 10, 0);
return sectionEdgeInsets;
}
#pragma mark - getter and setter
- (UICollectionView *)myCollectionView
{
if (_myCollectionView == nil) {
DMHeaderFlowLayout *headerFlowLayout = [[DMHeaderFlowLayout alloc] init];
headerFlowLayout.scrollDirection = UICollectionViewScrollDirectionVertical;
_myCollectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(0, 64, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height - 64) collectionViewLayout:headerFlowLayout];
_myCollectionView.dataSource = self;
_myCollectionView.backgroundColor = [UIColor whiteColor];
_myCollectionView.delegate = self;
[_myCollectionView registerClass:[DMSectionHeaderCollectionReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:kDMSectionHeaderCollectionReusableViewHeaderFlowIdentifier];
[_myCollectionView registerClass:[DMSectionHeaderCollectionReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:kDMSectionHeaderCollectionReusableViewFooterFlowIdentifier];
[_myCollectionView registerClass:[DMNormalCollectionViewCell class] forCellWithReuseIdentifier:kDMNormalCollectionViewCellHeaderFlowIdentifier];
}
return _myCollectionView;
}
- (NSMutableArray *)dataArray
{
if (_dataArray == nil) {
_dataArray = [[NSMutableArray alloc] init];
}
return _dataArray;
}
@end
效果如下
悬浮sectionHeader.gif
我们知道分为三个section,前两个section有section header,会悬浮,最后一个section没有section header,所以就不会悬浮啦
网友评论