UICollectionView的灵活布局 –从一个需求谈起

综合技术 小专栏 (源链)

一切的起源在于一个这样的布局需求。

首先就想到collectionView。用tableView也能强行实现这个就是了,但是比较笨重,改动布局就得重画cell,所以本文就详细介绍下我怎么实现这个需求的。

此处先放点新手福利

如果你没接触过UICollectionView,但对UITableView比较熟悉的,可以看下这段,熟悉UICollectionView可以直接跳过。连UITableView都不熟的建议从基础开始学。

绘制UICollectionView类似于UITableView,满足delegate和dataSouce两个代理。

注意的区别是:

1、UICollectionView的indexPath,通常使用item的属性,印象中row的属性跟UITableView不一样,和section毫无关系。

2、每个section的head和footViewz在这个代理里实现。

  • – ( UICollectionReusableView *)collectionView:( UICollectionView *)collectionView viewForSupplementaryElementOfKind:( NSString *)kind atIndexPath:( NSIndexPath *)indexPath;

这个view可以像cell一样复用的。在初始化时注册:

  • [ self .collectionView registerClass:[ UICollectionReusableView class ] forSupplementaryViewOfKind: UICollectionElementKindSectionHeader withReuseIdentifier: @”UICollectionElementKindSectionHeader” ];

使用时:

- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { 
 UICollectionReusableView *reusableview = nil; 
 if (kind == UICollectionElementKindSectionHeader) { 
 UICollectionReusableView *headerView = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"UICollectionElementKindSectionHeader" forIndexPath:indexPath]; 
 headerView.backgroundColor = [UIColor redColor]; 
 reusableview = headerView; 
} 
return reusableview; 

3、UICollectionView的cell选中的时候是有backgroundView和selectedBackgroundView两个属性的,可以做出一些选择效果:

@property (nonatomic, strong, nullable) UIView *backgroundView; 
@property (nonatomic, strong, nullable) UIView *selectedBackgroundView; 

4、每个UICollectionView初始化都要有个UICollectionViewLayout来实现布局,用系统自带的布局UICollectionViewDelegateFlowLayout的话,满足这个代理设置高度:

  • – ( CGSize )collectionView:( UICollectionView *)collectionView layout:( UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:( NSIndexPath *)indexPath

UICollectionViewDelegateFlowLayout里还有好几个参数和其他的代理,有兴趣的同学可以去看看api,然后写个demo测试一下,因为蛮简单的,这里就不再深究了。。

新手福利结束

回归正题

现在回到我我们的需求上。

满足这个需求我们必须重写UICollectionViewLayout。

核心重写的方法如下:

//每次布局都会调用 
- (void)prepareLayout; 
//布局完成后设置contentSize 
- (CGSize)collectionViewContentSize; 
//返回每个item的属性 
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath; 
//返回所有item属性 
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect 

当然最重要的是,你的一些参数得通过代理或者Block传出来赋值。

/* 
* 获取item宽高 
* 
* @param block 返回宽高的block 
*/ 
- (void)calculateItemSizeWithWidthBlock:(CGSize (^)(NSIndexPath *indexPath))block; 

下面是代码,注释丰富,可以放心阅读:

HomeCollectionLayout.h:

typedef CGSize(^SizeBlock)(NSIndexPath *indexPath); 
 
@interface HomeCollectionLayout : UICollectionViewLayout 
 
/** 行间距 */ 
@property (nonatomic, assign) CGFloat rowSpacing; 
/** 列间距 */ 
@property (nonatomic, assign) CGFloat lineSpacing; 
/** 内边距 */ 
@property (nonatomic, assign) UIEdgeInsets sectionInset; 
 
/* 
* 获取item宽高 
* 
* @param block 返回宽高的block 
*/ 
- (void)calculateItemSizeWithWidthBlock:(CGSize (^)(NSIndexPath *indexPath))block; 

HomeCollectionLayout.m

@interface HomeCollectionLayout() 
/** 计算每个item高度的block,必须实现*/ 
@property (nonatomic, copy) SizeBlock block; 
 
/** 存放元素高宽的键值对 */ 
@property (nonatomic, strong) NSMutableArray *arrOfSize; 
/**存放所有item的attrubutes属性 */ 
@property (nonatomic, strong) NSMutableArray *array; 
/**存放所有section的高度的 */ 
@property (nonatomic, strong) NSMutableArray *arrOfSectionHeight; 
 
/**总section高度,用于直接输出contentSize */ 
@property (nonatomic,assign) CGFloat collectionSizeHeight; 
/**总共item个数 */ 
@property (nonatomic,assign) NSInteger itemCount; 
 
@property (nonatomic,assign) CGFloat collectionWidth; 
 
@end 
 
@implementation HomeCollectionLayout 
- (instancetype)init 
{ 
 self = [super init]; 
 if (self) { 
 //对默认属性进行设置 
 _arrOfSize = [NSMutableArray array]; 
 _array = [NSMutableArray array]; 
 _arrOfSectionHeight = [NSMutableArray array]; 
 
 self.itemCount = 0; 
 
 self.collectionSizeHeight = 0; 
 
 self.sectionInset = UIEdgeInsetsMake(2, 0, 0, 0); 
 
 self.lineSpacing = 1; 
 self.rowSpacing = 1; 
 } 
 return self; 
} 
 
/** 
 * 准备好布局时调用 
 */ 
- (void)prepareLayout { 
 [super prepareLayout]; 
 
 //reload的时候清空原有数据 
 [_array removeAllObjects]; 
 [_arrOfSize removeAllObjects]; 
 [_arrOfSectionHeight removeAllObjects]; 
 _collectionSizeHeight = 0; 
 _itemCount = 0; 
 
 NSInteger sectionCount = [self.collectionView numberOfSections]; 
 //根据每个indexPath储存 
 for (NSInteger i = 0 ; i < sectionCount; i++) { 
 NSInteger rowCount = [self.collectionView numberOfItemsInSection:i]; 
 //存储item的总数目 
 self.itemCount += rowCount; 
 //存储每个列数的长度 
 NSMutableDictionary *dict = [NSMutableDictionary dictionary]; 
 
 //计算该section列数 
 NSInteger lines = 0; 
 CGSize size = CGSizeZero; 
 if (self.block != nil) { 
 size = self.block([NSIndexPath indexPathForRow:0 inSection:i]); 
 }else{ 
 NSAssert(size.width != 0 ,@"未实现block"); 
 } 
 lines = self.collectionWidth/size.width; 
 
 //存储每个列数的长度 
 for (NSInteger k = 0; k < lines; k++) { 
 [dict setObject:@(self.sectionInset.top) forKey:[NSString stringWithFormat:@"%ld",(long)k]]; 
 } 
 [_arrOfSize addObject:dict]; 
 
 for (NSInteger j = 0; j < rowCount; j++) { 
 NSIndexPath *indexPath = [NSIndexPath indexPathForItem:j inSection:i]; 
 //调用item计算。 
 [_array addObject:[self layoutAttributesForItemAtIndexPath:indexPath]]; 
 } 
 
 //此时dict已经改变 
 NSMutableDictionary *mdict = _arrOfSize[i]; 
 //计算每个section的高度 
 __block NSString *maxHeightline = @"0"; 
 [mdict enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSNumber *obj, BOOL *stop) { 
 if ([mdict[maxHeightline] floatValue]  [obj floatValue]) { 
 lineMinHeight = key; 
 } 
 }]; 
 int line = [lineMinHeight intValue]; 
 
 
 //找出最短行后,计算item位置 
 frame.origin = CGPointMake(line * (size.width + self.lineSpacing), [dict[lineMinHeight] floatValue] + self.collectionSizeHeight); 
 dict[lineMinHeight] = @(frame.size.height + self.rowSpacing + [dict[lineMinHeight] floatValue]); 
 //存储高度 
 [_arrOfSize replaceObjectAtIndex:indexPath.section withObject:dict]; 
 attr.frame = frame; 
 
 NSLog(@"nframe = %@,indexPath = %@nn",NSStringFromCGRect(frame),indexPath); 
 
 
 
 return attr; 
} 
/** 
 * 返回视图框内item的属性,可以直接返回所有item属性 
 */ 
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { 
 return _array; 
} 
 
#pragma mark - data source 
 
/** 
 * 设置计算高度block方法 
 * 
 * @param block 计算item高度的block 
 */ 
- (void)calculateItemSizeWithWidthBlock:(CGSize (^)(NSIndexPath *indexPath))block { 
 if (self.block != block) { 
 self.block = block; 
 } 
} 
 
#pragma mark - getter & setter 
- (CGFloat)collectionWidth { 
 return self.collectionView.frame.size.width; 
} 
@end 

demo地址: 本文demo

小结:跟一般瀑布流不同,这种布局collectionItem的size全部要自己定制,比起强行画来说,这么做以后更好改。就是算死我了,算法还是需要加强。

另外,这里另外一名作者 Tuberose 写了篇更详细的关于瀑布流的文章:

想更深入研究的同学可以移步这里: 瀑布流小框架

本文来自大灰灰的小专栏: https://xiaozhuanlan.com/topic/3247869501

一切的起源在于一个这样的布局需求。

首先就想到collectionView。用tableView也能强行实现这个就是了,但是比较笨重,改动布局就得重画cell,所以本文就详细介绍下我怎么实现这个需求的。

此处先放点新手福利

如果你没接触过UICollectionView,但对UITableView比较熟悉的,可以看下这段,熟悉UICollectionView可以直接跳过。连UITableView都不熟的建议从基础开始学。

绘制UICollectionView类似于UITableView,满足delegate和dataSouce两个代理。

注意的区别是:

1、UICollectionView的indexPath,通常使用item的属性,印象中row的属性跟UITableView不一样,和section毫无关系。

2、每个section的head和footViewz在这个代理里实现。

  • – ( UICollectionReusableView *)collectionView:( UICollectionView *)collectionView viewForSupplementaryElementOfKind:( NSString *)kind atIndexPath:( NSIndexPath *)indexPath;

这个view可以像cell一样复用的。在初始化时注册:

  • [ self .collectionView registerClass:[ UICollectionReusableView class ] forSupplementaryViewOfKind: UICollectionElementKindSectionHeader withReuseIdentifier: @”UICollectionElementKindSectionHeader” ];

使用时:

- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { 
 UICollectionReusableView *reusableview = nil; 
 if (kind == UICollectionElementKindSectionHeader) { 
 UICollectionReusableView *headerView = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"UICollectionElementKindSectionHeader" forIndexPath:indexPath]; 
 headerView.backgroundColor = [UIColor redColor]; 
 reusableview = headerView; 
} 
return reusableview; 

3、UICollectionView的cell选中的时候是有backgroundView和selectedBackgroundView两个属性的,可以做出一些选择效果:

@property (nonatomic, strong, nullable) UIView *backgroundView; 
@property (nonatomic, strong, nullable) UIView *selectedBackgroundView; 

4、每个UICollectionView初始化都要有个UICollectionViewLayout来实现布局,用系统自带的布局UICollectionViewDelegateFlowLayout的话,满足这个代理设置高度:

  • – ( CGSize )collectionView:( UICollectionView *)collectionView layout:( UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:( NSIndexPath *)indexPath

UICollectionViewDelegateFlowLayout里还有好几个参数和其他的代理,有兴趣的同学可以去看看api,然后写个demo测试一下,因为蛮简单的,这里就不再深究了。。

新手福利结束

回归正题

现在回到我我们的需求上。

满足这个需求我们必须重写UICollectionViewLayout。

核心重写的方法如下:

//每次布局都会调用 
- (void)prepareLayout; 
//布局完成后设置contentSize 
- (CGSize)collectionViewContentSize; 
//返回每个item的属性 
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath; 
//返回所有item属性 
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect 

当然最重要的是,你的一些参数得通过代理或者Block传出来赋值。

/* 
* 获取item宽高 
* 
* @param block 返回宽高的block 
*/ 
- (void)calculateItemSizeWithWidthBlock:(CGSize (^)(NSIndexPath *indexPath))block; 

下面是代码,注释丰富,可以放心阅读:

HomeCollectionLayout.h:

typedef CGSize(^SizeBlock)(NSIndexPath *indexPath); 
 
@interface HomeCollectionLayout : UICollectionViewLayout 
 
/** 行间距 */ 
@property (nonatomic, assign) CGFloat rowSpacing; 
/** 列间距 */ 
@property (nonatomic, assign) CGFloat lineSpacing; 
/** 内边距 */ 
@property (nonatomic, assign) UIEdgeInsets sectionInset; 
 
/* 
* 获取item宽高 
* 
* @param block 返回宽高的block 
*/ 
- (void)calculateItemSizeWithWidthBlock:(CGSize (^)(NSIndexPath *indexPath))block; 

HomeCollectionLayout.m

@interface HomeCollectionLayout() 
/** 计算每个item高度的block,必须实现*/ 
@property (nonatomic, copy) SizeBlock block; 
 
/** 存放元素高宽的键值对 */ 
@property (nonatomic, strong) NSMutableArray *arrOfSize; 
/**存放所有item的attrubutes属性 */ 
@property (nonatomic, strong) NSMutableArray *array; 
/**存放所有section的高度的 */ 
@property (nonatomic, strong) NSMutableArray *arrOfSectionHeight; 
 
/**总section高度,用于直接输出contentSize */ 
@property (nonatomic,assign) CGFloat collectionSizeHeight; 
/**总共item个数 */ 
@property (nonatomic,assign) NSInteger itemCount; 
 
@property (nonatomic,assign) CGFloat collectionWidth; 
 
@end 
 
@implementation HomeCollectionLayout 
- (instancetype)init 
{ 
 self = [super init]; 
 if (self) { 
 //对默认属性进行设置 
 _arrOfSize = [NSMutableArray array]; 
 _array = [NSMutableArray array]; 
 _arrOfSectionHeight = [NSMutableArray array]; 
 
 self.itemCount = 0; 
 
 self.collectionSizeHeight = 0; 
 
 self.sectionInset = UIEdgeInsetsMake(2, 0, 0, 0); 
 
 self.lineSpacing = 1; 
 self.rowSpacing = 1; 
 } 
 return self; 
} 
 
/** 
 * 准备好布局时调用 
 */ 
- (void)prepareLayout { 
 [super prepareLayout]; 
 
 //reload的时候清空原有数据 
 [_array removeAllObjects]; 
 [_arrOfSize removeAllObjects]; 
 [_arrOfSectionHeight removeAllObjects]; 
 _collectionSizeHeight = 0; 
 _itemCount = 0; 
 
 NSInteger sectionCount = [self.collectionView numberOfSections]; 
 //根据每个indexPath储存 
 for (NSInteger i = 0 ; i < sectionCount; i++) { 
 NSInteger rowCount = [self.collectionView numberOfItemsInSection:i]; 
 //存储item的总数目 
 self.itemCount += rowCount; 
 //存储每个列数的长度 
 NSMutableDictionary *dict = [NSMutableDictionary dictionary]; 
 
 //计算该section列数 
 NSInteger lines = 0; 
 CGSize size = CGSizeZero; 
 if (self.block != nil) { 
 size = self.block([NSIndexPath indexPathForRow:0 inSection:i]); 
 }else{ 
 NSAssert(size.width != 0 ,@"未实现block"); 
 } 
 lines = self.collectionWidth/size.width; 
 
 //存储每个列数的长度 
 for (NSInteger k = 0; k < lines; k++) { 
 [dict setObject:@(self.sectionInset.top) forKey:[NSString stringWithFormat:@"%ld",(long)k]]; 
 } 
 [_arrOfSize addObject:dict]; 
 
 for (NSInteger j = 0; j < rowCount; j++) { 
 NSIndexPath *indexPath = [NSIndexPath indexPathForItem:j inSection:i]; 
 //调用item计算。 
 [_array addObject:[self layoutAttributesForItemAtIndexPath:indexPath]]; 
 } 
 
 //此时dict已经改变 
 NSMutableDictionary *mdict = _arrOfSize[i]; 
 //计算每个section的高度 
 __block NSString *maxHeightline = @"0"; 
 [mdict enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSNumber *obj, BOOL *stop) { 
 if ([mdict[maxHeightline] floatValue]  [obj floatValue]) { 
 lineMinHeight = key; 
 } 
 }]; 
 int line = [lineMinHeight intValue]; 
 
 
 //找出最短行后,计算item位置 
 frame.origin = CGPointMake(line * (size.width + self.lineSpacing), [dict[lineMinHeight] floatValue] + self.collectionSizeHeight); 
 dict[lineMinHeight] = @(frame.size.height + self.rowSpacing + [dict[lineMinHeight] floatValue]); 
 //存储高度 
 [_arrOfSize replaceObjectAtIndex:indexPath.section withObject:dict]; 
 attr.frame = frame; 
 
 NSLog(@"nframe = %@,indexPath = %@nn",NSStringFromCGRect(frame),indexPath); 
 
 
 
 return attr; 
} 
/** 
 * 返回视图框内item的属性,可以直接返回所有item属性 
 */ 
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { 
 return _array; 
} 
 
#pragma mark - data source 
 
/** 
 * 设置计算高度block方法 
 * 
 * @param block 计算item高度的block 
 */ 
- (void)calculateItemSizeWithWidthBlock:(CGSize (^)(NSIndexPath *indexPath))block { 
 if (self.block != block) { 
 self.block = block; 
 } 
} 
 
#pragma mark - getter & setter 
- (CGFloat)collectionWidth { 
 return self.collectionView.frame.size.width; 
} 
@end 

demo地址: 本文demo

小结:跟一般瀑布流不同,这种布局collectionItem的size全部要自己定制,比起强行画来说,这么做以后更好改。就是算死我了,算法还是需要加强。

另外,这里另外一名作者 Tuberose 写了篇更详细的关于瀑布流的文章:

想更深入研究的同学可以移步这里: 瀑布流小框架

本文来自大灰灰的小专栏: https://xiaozhuanlan.com/topic/3247869501

一切的起源在于一个这样的布局需求。

首先就想到collectionView。用tableView也能强行实现这个就是了,但是比较笨重,改动布局就得重画cell,所以本文就详细介绍下我怎么实现这个需求的。

此处先放点新手福利

如果你没接触过UICollectionView,但对UITableView比较熟悉的,可以看下这段,熟悉UICollectionView可以直接跳过。连UITableView都不熟的建议从基础开始学。

绘制UICollectionView类似于UITableView,满足delegate和dataSouce两个代理。

注意的区别是:

1、UICollectionView的indexPath,通常使用item的属性,印象中row的属性跟UITableView不一样,和section毫无关系。

2、每个section的head和footViewz在这个代理里实现。

  • – ( UICollectionReusableView *)collectionView:( UICollectionView *)collectionView viewForSupplementaryElementOfKind:( NSString *)kind atIndexPath:( NSIndexPath *)indexPath;

这个view可以像cell一样复用的。在初始化时注册:

  • [ self .collectionView registerClass:[ UICollectionReusableView class ] forSupplementaryViewOfKind: UICollectionElementKindSectionHeader withReuseIdentifier: @”UICollectionElementKindSectionHeader” ];

使用时:

- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { 
 UICollectionReusableView *reusableview = nil; 
 if (kind == UICollectionElementKindSectionHeader) { 
 UICollectionReusableView *headerView = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"UICollectionElementKindSectionHeader" forIndexPath:indexPath]; 
 headerView.backgroundColor = [UIColor redColor]; 
 reusableview = headerView; 
} 
return reusableview; 

3、UICollectionView的cell选中的时候是有backgroundView和selectedBackgroundView两个属性的,可以做出一些选择效果:

@property (nonatomic, strong, nullable) UIView *backgroundView; 
@property (nonatomic, strong, nullable) UIView *selectedBackgroundView; 

4、每个UICollectionView初始化都要有个UICollectionViewLayout来实现布局,用系统自带的布局UICollectionViewDelegateFlowLayout的话,满足这个代理设置高度:

  • – ( CGSize )collectionView:( UICollectionView *)collectionView layout:( UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:( NSIndexPath *)indexPath

UICollectionViewDelegateFlowLayout里还有好几个参数和其他的代理,有兴趣的同学可以去看看api,然后写个demo测试一下,因为蛮简单的,这里就不再深究了。。

新手福利结束

回归正题

现在回到我我们的需求上。

满足这个需求我们必须重写UICollectionViewLayout。

核心重写的方法如下:

//每次布局都会调用 
- (void)prepareLayout; 
//布局完成后设置contentSize 
- (CGSize)collectionViewContentSize; 
//返回每个item的属性 
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath; 
//返回所有item属性 
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect 

当然最重要的是,你的一些参数得通过代理或者Block传出来赋值。

/* 
* 获取item宽高 
* 
* @param block 返回宽高的block 
*/ 
- (void)calculateItemSizeWithWidthBlock:(CGSize (^)(NSIndexPath *indexPath))block; 

下面是代码,注释丰富,可以放心阅读:

HomeCollectionLayout.h:

typedef CGSize(^SizeBlock)(NSIndexPath *indexPath); 
 
@interface HomeCollectionLayout : UICollectionViewLayout 
 
/** 行间距 */ 
@property (nonatomic, assign) CGFloat rowSpacing; 
/** 列间距 */ 
@property (nonatomic, assign) CGFloat lineSpacing; 
/** 内边距 */ 
@property (nonatomic, assign) UIEdgeInsets sectionInset; 
 
/* 
* 获取item宽高 
* 
* @param block 返回宽高的block 
*/ 
- (void)calculateItemSizeWithWidthBlock:(CGSize (^)(NSIndexPath *indexPath))block; 

HomeCollectionLayout.m

@interface HomeCollectionLayout() 
/** 计算每个item高度的block,必须实现*/ 
@property (nonatomic, copy) SizeBlock block; 
 
/** 存放元素高宽的键值对 */ 
@property (nonatomic, strong) NSMutableArray *arrOfSize; 
/**存放所有item的attrubutes属性 */ 
@property (nonatomic, strong) NSMutableArray *array; 
/**存放所有section的高度的 */ 
@property (nonatomic, strong) NSMutableArray *arrOfSectionHeight; 
 
/**总section高度,用于直接输出contentSize */ 
@property (nonatomic,assign) CGFloat collectionSizeHeight; 
/**总共item个数 */ 
@property (nonatomic,assign) NSInteger itemCount; 
 
@property (nonatomic,assign) CGFloat collectionWidth; 
 
@end 
 
@implementation HomeCollectionLayout 
- (instancetype)init 
{ 
 self = [super init]; 
 if (self) { 
 //对默认属性进行设置 
 _arrOfSize = [NSMutableArray array]; 
 _array = [NSMutableArray array]; 
 _arrOfSectionHeight = [NSMutableArray array]; 
 
 self.itemCount = 0; 
 
 self.collectionSizeHeight = 0; 
 
 self.sectionInset = UIEdgeInsetsMake(2, 0, 0, 0); 
 
 self.lineSpacing = 1; 
 self.rowSpacing = 1; 
 } 
 return self; 
} 
 
/** 
 * 准备好布局时调用 
 */ 
- (void)prepareLayout { 
 [super prepareLayout]; 
 
 //reload的时候清空原有数据 
 [_array removeAllObjects]; 
 [_arrOfSize removeAllObjects]; 
 [_arrOfSectionHeight removeAllObjects]; 
 _collectionSizeHeight = 0; 
 _itemCount = 0; 
 
 NSInteger sectionCount = [self.collectionView numberOfSections]; 
 //根据每个indexPath储存 
 for (NSInteger i = 0 ; i < sectionCount; i++) { 
 NSInteger rowCount = [self.collectionView numberOfItemsInSection:i]; 
 //存储item的总数目 
 self.itemCount += rowCount; 
 //存储每个列数的长度 
 NSMutableDictionary *dict = [NSMutableDictionary dictionary]; 
 
 //计算该section列数 
 NSInteger lines = 0; 
 CGSize size = CGSizeZero; 
 if (self.block != nil) { 
 size = self.block([NSIndexPath indexPathForRow:0 inSection:i]); 
 }else{ 
 NSAssert(size.width != 0 ,@"未实现block"); 
 } 
 lines = self.collectionWidth/size.width; 
 
 //存储每个列数的长度 
 for (NSInteger k = 0; k < lines; k++) { 
 [dict setObject:@(self.sectionInset.top) forKey:[NSString stringWithFormat:@"%ld",(long)k]]; 
 } 
 [_arrOfSize addObject:dict]; 
 
 for (NSInteger j = 0; j < rowCount; j++) { 
 NSIndexPath *indexPath = [NSIndexPath indexPathForItem:j inSection:i]; 
 //调用item计算。 
 [_array addObject:[self layoutAttributesForItemAtIndexPath:indexPath]]; 
 } 
 
 //此时dict已经改变 
 NSMutableDictionary *mdict = _arrOfSize[i]; 
 //计算每个section的高度 
 __block NSString *maxHeightline = @"0"; 
 [mdict enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSNumber *obj, BOOL *stop) { 
 if ([mdict[maxHeightline] floatValue]  [obj floatValue]) { 
 lineMinHeight = key; 
 } 
 }]; 
 int line = [lineMinHeight intValue]; 
 
 
 //找出最短行后,计算item位置 
 frame.origin = CGPointMake(line * (size.width + self.lineSpacing), [dict[lineMinHeight] floatValue] + self.collectionSizeHeight); 
 dict[lineMinHeight] = @(frame.size.height + self.rowSpacing + [dict[lineMinHeight] floatValue]); 
 //存储高度 
 [_arrOfSize replaceObjectAtIndex:indexPath.section withObject:dict]; 
 attr.frame = frame; 
 
 NSLog(@"nframe = %@,indexPath = %@nn",NSStringFromCGRect(frame),indexPath); 
 
 
 
 return attr; 
} 
/** 
 * 返回视图框内item的属性,可以直接返回所有item属性 
 */ 
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { 
 return _array; 
} 
 
#pragma mark - data source 
 
/** 
 * 设置计算高度block方法 
 * 
 * @param block 计算item高度的block 
 */ 
- (void)calculateItemSizeWithWidthBlock:(CGSize (^)(NSIndexPath *indexPath))block { 
 if (self.block != block) { 
 self.block = block; 
 } 
} 
 
#pragma mark - getter & setter 
- (CGFloat)collectionWidth { 
 return self.collectionView.frame.size.width; 
} 
@end 

demo地址: 本文demo

小结:跟一般瀑布流不同,这种布局collectionItem的size全部要自己定制,比起强行画来说,这么做以后更好改。就是算死我了,算法还是需要加强。

另外,这里另外一名作者 Tuberose 写了篇更详细的关于瀑布流的文章:

想更深入研究的同学可以移步这里: 瀑布流小框架

本文来自大灰灰的小专栏: https://xiaozhuanlan.com/topic/3247869501

您可能感兴趣的

The resized image taken by the iOS camera does not... My problem is that after the image is shot, it scales itself thus it does not seem exactly the same view what I would expect by looking at the camera ...
Creating Today Widgets Today widgets are small view controllers that your users can add to their ‘Today’ view. They are good for displaying the most relevant information t...
2018 美团春招 iOS面试分享(一面) 2018 美团春招 iOS面试分享(一面) 说来惭愧,实习准备开始的太晚了。我是从3.5号开始准备的(各种原因,导师可能不放,论文第二个点没做完之类的),大致刷的是OC各种基础,牛客上面剑指offer一半的题目,常用排序和数据结构。昨晚接到的开发电话,说约个面试吧。当时心里一惊,因为没有HR通知...
Advanced Dependency Injection In mylast post, I showed you how to use setter dependency injection to mock an Apple built-in class by: Creating a super simple test double ...
Memory problem when trying to back up huge data in... I am trying to save multiple images to my documents directory after capturing them from the camera. Its about 50 images that I am trying to save. But ...
小专栏责编内容来自:小专栏 (源链) | 更多关于

阅读提示:酷辣虫无法对本内容的真实性提供任何保证,请自行验证并承担相关的风险与后果!
本站遵循[CC BY-NC-SA 4.0]。如您有版权、意见投诉等问题,请通过eMail联系我们处理。
酷辣虫 » UICollectionView的灵活布局 –从一个需求谈起



专业 x 专注 x 聚合 x 分享 CC BY-NC-SA 4.0

使用声明 | 英豪名录