UICollectionView机制和UITableView类似,不同的是UICollectionView有一个专门用于布局的类UICollectionViewFlowLayout类.

通过自定义这个类,可以实现自定义布局.

所谓的瀑布流布局,就是值视图中的网格宽度一致,而高度不同,类似于下图所示

android 滑动 瀑布流 uicollectionview瀑布流_UICollectionView

具体的实现可以通过一个继承自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