IOS开发 多section瀑布流+悬停Header OC


写在前面的话

问题:在实习期间才开始学习IOS开发,项目中要求实现类似小红书首页效果的瀑布流,包括多种Section,每种Section有对应的头部HeaderView,以及上滑时实现Header悬停。

解决办法:在网上搜索了很多资料,乱七八糟的,很多讲的迷迷糊糊而且代码没有注释!!这对一个ios开发的新手来说是极其不友好的。不过我还是发现了一篇比较不错的文章,但是他只实现了一种Section的瀑布流,并不足够完成我的项目,然后我就继续搜索重复的做无用功,最后的结果还是。。。不行。但是我并没有放弃,开始自己搞,最后终于搞懂里面的原理,并最终实现了我需要的效果,下面我将提供给大家一种思路去解决以上问题。

正文

    首先了解如何实现瀑布流

    通过查资料,实现瀑布流的方法有多种,最常见的就是用UICollectionView去实现,因此我就采取这种方法去实现它。

    如何通过UICollectionView实现瀑布流

    认真了解过UICollectionView后,你肯定知道UICollectionView的初始化,必须要有一个Layout


collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];

即collectionViewLayout: layout。而实现瀑布流的关键就在这个Layout!!!通过自定义的CollectionViewLayout就可以实现瀑布流其他各种操作,下面介绍自定义Layout

    先贴代码,然后再去分析
//  FallsFlowLayout.h文件
#import <UIKit/UIKit.h>

@interface FallsFlowLayout : UICollectionViewLayout

@property (nonatomic, assign, readonly) CGFloat itemWidth;//单元格宽度
@property (nonatomic, assign) NSInteger numberOfColumns;//列数:默认为2
@property (nonatomic, assign) UIEdgeInsets insets;//内边距 : 每一列之间的间距 (top, left, bottom, right)默认为{10, 10, 10, 10};
@property (nonatomic, assign) CGFloat rowGap;//每一行之间的间距 : 默认为10
@property (nonatomic, assign) CGFloat columnGap;//每一列之间的间距 : 默认为10
@property (nonatomic, strong) NSMutableArray *itemHeights;//高度数组 : 存储所有item的高度
@property (nonatomic, assign) NSUInteger sectionCount;// section数目,默认为2

@end

// FallsFlowLayout.m文件

#import "FallsFlowLayout.h"
#import "UIUtils.h" //这个是我自定义的工具类,这里主要是取kScreenWidth(屏幕宽度)

@interface FallsFlowLayout()

@property (nonatomic, strong) NSMutableArray *itemAttributes; // 存放每个cell的布局属性
@property (nonatomic, strong) NSMutableArray *columnsHeightsOfSection; // 每个Section的每一列的高度,二维数组
@property (nonatomic, assign) NSInteger stickHeight;//悬停Header的高度

@end

@implementation FallsFlowLayout

#pragma mark- 懒加载
- (NSMutableArray *)columnsHeightsOfSection {
if (!_columnsHeightsOfSection) {
_columnsHeightsOfSection = [NSMutableArray array];
}
return _columnsHeightsOfSection;
}

- (NSMutableArray *)itemAttributes {
if (!_itemAttributes) {
_itemAttributes= [NSMutableArray array];
}
return _itemAttributes;
}

#pragma mark- 初始化方法
- (instancetype)init {
if(self= [super init]) {
// 初始化默认值
self.numberOfColumns = 2;//默认列数为2
self.columnGap=10;//默认列间距为10
self.rowGap=10;//默认行间距为10
self.insets = UIEdgeInsetsMake(10, 10, 10, 10);//默认UICollectionView的内边距为10
self.stickHeight=44;//悬停的Header高度
self.sectionCount=2;//默认为2
}
return self;
}

#pragma mark- get方法

/// 获取单元格宽度
- (CGFloat)itemWidth {
//( collectionView的宽度 - 列间距*列数 ) / 列数
return (self.collectionView.frame.size.width - (self.numberOfColumns + 1)*self.columnGap) / self.numberOfColumns;
}

/// 获取某个section的最短列的编号
/// @param section 分区
- (NSUInteger)minIndexOfSection:(NSUInteger)section {
NSInteger minIndex =0;
CGFloat minHeight =MAXFLOAT;
for(NSInteger i =0; i <self.numberOfColumns; i ++) {
// 取出某一列的高度与其他的比较,求得高度最短的列的 列号
CGFloat currentHeight = [self.columnsHeightsOfSection[section][i]floatValue];
if(currentHeight < minHeight) {
minHeight = currentHeight;
minIndex = i;
}
}
return minIndex;
}

/// 获取某个section的最短列的编号
/// @param section 分区
- (NSUInteger)maxIndexOfSection:(NSUInteger)section {
NSInteger maxIndex =0;
CGFloat maxHeight =0;
for(NSInteger i =0; i <self.numberOfColumns; i ++) {
CGFloat currentHeight = [self.columnsHeightsOfSection[section][i]floatValue];
if(currentHeight > maxHeight) {
maxHeight = currentHeight;
maxIndex = i;
}
}
return maxIndex;
}

/// 获取某个section的最短高度
/// @param section 分区
- (CGFloat)minHeightOfSection:(NSUInteger)section {
// 该section 最短列的列号
NSUInteger minIndex = [self minIndexOfSection:section];

return[self.columnsHeightsOfSection[section][minIndex]floatValue];
}

/// 获取某个section的最大高度
/// @param section 分区
- (CGFloat)maxHeightOfSection:(NSUInteger)section {
NSUInteger maxIndex = [self maxIndexOfSection:section];

return[self.columnsHeightsOfSection[section][maxIndex]floatValue];
}

#pragma mark- 系统内部方法
/// 重写父类布局
- (void)prepareLayout {
[super prepareLayout];

// 重置每一个Section的每一列的最大Y值为Header高度
[self.columnsHeightsOfSection removeAllObjects];
for(inti =0;i <self.sectionCount;i++){
NSMutableArray*columnsHeights = [NSMutableArray array];
for(intj =0;j <self.numberOfColumns;j++){
// 每个Section的Header高度,我的Header高度为44
[columnsHeightsaddObject:@(44)];
}
[self.columnsHeightsOfSection addObject:columnsHeights];
}

// 计算所有cell的布局属性,包括HeaderView的,先清空
[self.itemAttributes removeAllObjects];

// Header
NSMutableArray<UICollectionViewLayoutAttributes *> *layoutHeader = [NSMutableArray arrayWithCapacity:self.sectionCount];

// 跑这个Section循环是为了实现多Section
for(int section =0;section <self.sectionCount;section++)
{
// 头部视图
layoutHeader[section] = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader withIndexPath:[NSIndexPath indexPathWithIndex:section]];

// Header的y坐标为 前面Section的最大高度之和
int y =0;
for(int i =0;i < section;i++)
y += [self maxHeightOfSection:i];

// 我的HeaderView的高度为44
layoutHeader[section].frame=CGRectMake(0,y,kScreenWidth,44);
// 将Header的属性添加到数组
[self.itemAttributes addObject:layoutHeader[section]];

// 计算所有cell的布局属性,看好下面的section
NSUInteger itemCount = [self.collectionView numberOfItemsInSection:section];
for(NSUIntegeri =0; i < itemCount; ++i) {
NSIndexPath*indexPath = [NSIndexPath indexPathForItem:i inSection:section];
[self setItemFrame:indexPath];
}
}
}

/// 设置每一个attrs的frame,并加入数组中
/// @param indexPath 第几个item
- (void)setItemFrame:(NSIndexPath*)indexPath {
/**
* 注:1.cell的宽度和高度算起来比较简单 : 宽度固定(itemWidth已经算好),高度由外部传进来
* 2.cell的x : minIndex最短列作为当前列。
* 3.cell的y : 把前面所有Section的最大高度求和 + 该Section的最短列高度
*/

UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];

// cell的宽度和高度
CGFloat w =self.itemWidth;
CGFloat h = [self.itemHeights[indexPath.item]floatValue];

// 最短列编号
NSUInteger minIndex = [self minIndexOfSection:indexPath.section];
CGFloat x =self.insets.left+ minIndex * (w +self.columnGap);

// 前面几个section的最大高度之和 + 该section的最短列高度
CGFloat topSectionHeight =0;
CGFloat minHeight =0;
for(inti =0;i < indexPath.section;i++)
topSectionHeight += [self maxHeightOfSection:i];

// 加上该section的最短列高度
minHeight += [self minHeightOfSection:indexPath.section];
CGFloaty =topSectionHeight + minHeight +self.rowGap;

attrs.frame=CGRectMake(x, y, w, h);

// 更新该Section的高度
self.columnsHeightsOfSection[indexPath.section][minIndex] =@(h + minHeight +self.rowGap);
[self.itemAttributes addObject:attrs];
}

/// 返回collectionView的尺寸
- (CGSize)collectionViewContentSize {
CGFloat maxHeight =0;
for(inti =0;i <self.sectionCount; i++){
maxHeight += [self maxHeightOfSection:i];
}
return CGSizeMake(self.collectionView.frame.size.width, maxHeight);
}

/// 所有元素(比如cell、补充控件、装饰控件)的布局属性
/// @param rect 尺寸
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
[self sectionHeaderStickCounter];//Header停留
return self.itemAttributes;
}

#pragma mark- Header 停留
/// Header停留
- (void)sectionHeaderStickCounter
{
for (UICollectionViewLayoutAttributes *layoutAttributes in self.itemAttributes) {
if ([layoutAttributes.representedElementKind isEqualToString:UICollectionElementKindSectionHeader]) {
CGPoint origin = layoutAttributes.frame.origin;

if(layoutAttributes.indexPath.section==0){
// 只要往上一滑动,就将Header的y修改为collectionView.contentOffset.y,至于为什么是这个数,可以打个Log输出看看结果

// self.collectionView.contentOffset.y 是相对于CollectionView的content的相对y移动
if (self.collectionView.contentOffset.y >= 0)
origin.y=self.collectionView.contentOffset.y;

// 如果滑动到该下一个Section的Header上来了,则固定第一个的Header的y值
if(self.collectionView.contentOffset.y>= [self maxHeightOfSection:0] -self.stickHeight)
origin.y= [self maxHeightOfSection:0] -self.stickHeight;
}else{
// 这是默认两个Section的情况,如果有更多种情况,自己计算一下,加几个else if就好了
if(self.collectionView.contentOffset.y>= [self maxHeightOfSection:0])
origin.y=self.collectionView.contentOffset.y;
}

CGFloatwidth = layoutAttributes.frame.size.width;
layoutAttributes.zIndex = 2048;//设置一个比cell的zIndex大的值,让他浮于其他Cell之上
layoutAttributes.frame= (CGRect){
.origin= origin,
.size=CGSizeMake(width, layoutAttributes.frame.size.height)
};
}
}
}

/// 不设置这里看不到悬停
/// @param newBoundsnewBounds description
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
{
return YES;
}

@end

4 .分析代码
还有一个部分,如何使用没有写,下次一起。
代码分析下次再分析,可以先复制代码试试,有问题可以加我VX:13864023718。

原创:https://www.panoramacn.com
源码网提供WordPress源码,帝国CMS源码discuz源码,微信小程序,小说源码,杰奇源码,thinkphp源码,ecshop模板源码,微擎模板源码,dede源码,织梦源码等。

专业搭建小说网站,小说程序,杰奇系列,微信小说系列,app系列小说

IOS开发 多section瀑布流+悬停Header OC

免责声明,若由于商用引起版权纠纷,一切责任均由使用者承担。

您必须遵守我们的协议,如果您下载了该资源行为将被视为对《免责声明》全部内容的认可-> 联系客服 投诉资源
www.panoramacn.com资源全部来自互联网收集,仅供用于学习和交流,请勿用于商业用途。如有侵权、不妥之处,请联系站长并出示版权证明以便删除。 敬请谅解! 侵权删帖/违法举报/投稿等事物联系邮箱:2640602276@qq.com
未经允许不得转载:书荒源码源码网每日更新网站源码模板! » IOS开发 多section瀑布流+悬停Header OC
关注我们小说电影免费看
关注我们,获取更多的全网素材资源,有趣有料!
120000+人已关注
分享到:
赞(0) 打赏

评论抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

您的打赏就是我分享的动力!

支付宝扫一扫打赏

微信扫一扫打赏