UICollectionView机制和UITableView类似,不同的是UICollectionView有一个专门用于布局的类UICollectionViewFlowLayout类.
通过自定义这个类,可以实现自定义布局.
所谓的瀑布流布局,就是值视图中的网格宽度一致,而高度不同,类似于下图所示
具体的实现可以通过一个继承自UICollectionViewFlowLayout或者UICollectionViewLayout的类来实现(前者继承自后者);
类中常用的方法有四个:
- (void)prepareLayout; ///布局的初始化操作都是在这个方法里面
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath; ///针对每个item的具体布局实现
- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect; ///所有item的布局属性集合
- (CGSize)collectionViewContentSize; ///返回collectionView的最大位移值,类似于scrollView.contentSize
我们首先需要两个数组,一个用于存储各列高度的最大值,一个用于存储各个item的属性,
然后可以在准备布局的方法中进行初始化,然后在针对每一个item进行布局的方法中进行计算,
计算原理是找出三列中的高度最小值的一列,然后将item布置在该列,然后更新高度数组,然后再重复判断,
最后获取每个item的frame,存入UICollectionViewLayoutAttributes中,最后再存入数组中,通过第三个方法传出去.
最后从高度数组获取高度的最大值,更新ViewContentSize.
需要注意的是,高度的最大值是根据item的frame计算的,而不是根据bounds计算的!
代码
#import "MyFlowLayout.h"
#define kWidth self.collectionView.frame.size.width
static const CGFloat rowMargin = 10; ///行间距
static const CGFloat columnMargin = 10; ///列间距
static const UIEdgeInsets insets = {10,10,10,10}; ///边距
static const int columnCount = 3; ///列数
@interface MyFlowLayout()
@property (nonatomic,strong) NSMutableArray *columnMaxYs; ///用于存储各列的y方向的最大值
@property (nonatomic,strong) NSMutableArray *attrsArray; ///用于存储各个item的属性
@end
@implementation MyFlowLayout
///懒加载
- (NSMutableArray *)columnMaxYs{
if (!_columnMaxYs) {
_columnMaxYs = [NSMutableArray array];
}
return _columnMaxYs;
}
- (NSMutableArray *)attrsArray{
if (!_attrsArray) {
_attrsArray = [NSMutableArray array];
}
return _attrsArray;
}
- (CGSize)collectionViewContentSize{
CGFloat destMaxY = [self.columnMaxYs[0] doubleValue]; ///获取高度数组中的最大值
for (int i = 0; i<self.columnMaxYs.count; i++) {
CGFloat columnMaxY = [self.columnMaxYs[i] doubleValue];
if (destMaxY < columnMaxY) {
destMaxY = columnMaxY;
}
}
return CGSizeMake(0, destMaxY + insets.bottom); ///最大值加上边距的底部的值
}
- (void)prepareLayout{ ///初始布局
[super prepareLayout]; ///先调用父类方法
[self.columnMaxYs removeAllObjects]; ///首先清除数组数据
for (int i = 0; i<columnCount; i++) {
[self.columnMaxYs addObject:@(insets.top)];
}
[self.attrsArray removeAllObjects];
NSUInteger count = [self.collectionView numberOfItemsInSection:0]; ///获取item的数目
for (int i = 0; i<count; i++) { ///针对每一个item获取布局属性,存于数组
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
UICollectionViewLayoutAttributes *attrs = [self layoutAttributesForItemAtIndexPath:indexPath];
[self.attrsArray addObject:attrs];
}
}
///将数组中的个item的属性值传出去用以布局
- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect{
return self.attrsArray;
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath{
///获取该item的布局属性
UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
///计算X方向的item的间距
CGFloat xMargin = insets.left + insets.right + (columnCount - 1) * columnMargin;
///获取item的宽度
CGFloat w = (kWidth - xMargin) / columnCount;
///获取item的高度
CGFloat h = 50 + arc4random_uniform(150);
///获取高度数组的最小的一列
CGFloat destMaxY = [self.columnMaxYs[0] doubleValue];
NSUInteger destColumn = 0;
for (int i = 0; i<self.columnMaxYs.count; i++) {
CGFloat columnMaxY = [self.columnMaxYs[i] doubleValue];
if (destMaxY > columnMaxY) {
destMaxY = columnMaxY;
destColumn = i;
}
}
///然后计算item的x坐标和y坐标
CGFloat x = insets.left + destColumn * (w + columnMargin);
CGFloat y = destMaxY + rowMargin;
///将frame计算好赋予布局属性
attrs.frame = CGRectMake(x, y, w, h);
///更新该列的高度值
self.columnMaxYs[destColumn] = @(CGRectGetMaxY(attrs.frame));
return attrs;
}
@end