用collectionView实现瀑布流

在实现瀑布流之前,先来介绍下 collectionView 。collectionView 和 tableView 特别的像,但是比 tableView 多了布局。它运用一个灵活多变的布局呈现出你想要的效果。collectionView 用的最多的是用网格来展示数据(如下图所示),当然我们可以自定义布局,实现你能想象到的任何效果。

collectionView

collectionView 的组成:
效果界面:
collectionView

原理组成:
collectionView

从上图中可以看出整个 collectionView 由三种类型的视图组成:

Cell:collectionView 中最主要的展示部分,和 tableView 里的一样。

Supplementary View:展示头视图和尾视图,可以理解为 tableView 中的 sectionHeader 和 sectionFooter。

Decoration View:装饰视图,理解成背景就行了。

与 collectionView 相关联的对象之间的关系

collectionView

collectionView 的简单使用如下:

//初始化
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
UICollectionView *colView = [[UICollectionView alloc] initWithFrame:CGRectMake(0, 0, self.view.width, self.view.height - 64) collectionViewLayout:layout];
colView.backgroundColor = [UIColor grayColor];
[colView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"myCell"];
[colView registerClass:[ZWSupplementaryView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"header"];
[colView registerClass:[ZWSupplementaryView class] forSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:@"footer"];
colView.delegate = self;
colView.dataSource = self;
[self.view addSubview:colView];

代理方法:

#pragma mark UICollectionViewDataSource
//每个section里有多少个cell
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
    return 8;
}

//有多少个section
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
    return 4;
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *cellID = @"myCell";
    UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellID forIndexPath:indexPath];
    cell.backgroundColor = [UIColor yellowColor];
    return cell;
}

//headerView和footerView
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {

    NSString *reuseIdentifier;
    if ([kind isEqualToString: UICollectionElementKindSectionFooter]){
        reuseIdentifier = @"footer";
    } else {
        reuseIdentifier = @"header";
    }

    ZWSupplementaryView *view = [collectionView dequeueReusableSupplementaryViewOfKind :kind   withReuseIdentifier:reuseIdentifier forIndexPath:indexPath];

    if ([kind isEqualToString:UICollectionElementKindSectionHeader]){
        view.backgroundColor = [UIColor redColor];
        view.label.text = @"header";
    } else if ([kind isEqualToString:UICollectionElementKindSectionFooter]){
        view.backgroundColor = [UIColor greenColor];
        view.label.text = @"footer";
    }
    return view;
}

#pragma mark UICollectionViewDelegate
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
    NSLog(@"section:%ld,index:%ld",indexPath.section,(long)indexPath.row);
}

#pragma mark UICollectionViewDelegateFlowLayout
//定义每个UICollectionView 的大小
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
    return CGSizeMake(90, 100);
}

//定义每个UICollectionView的margin
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {
    return UIEdgeInsetsMake(5, 5, 5, 5);
}

//每个section中不同的行之间的行间距
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section {
    return 5;
}

//每个item之间的间距
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section {
    return 5;
}

//返回headerView的大小
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section {
    return CGSizeMake(collectionView.width, 50);
}

//返回footerView的大小
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section {
    return CGSizeMake(collectionView.width, 50);
}

在模拟器 iPhone 6s Plus 运行效果如下:

collectionView

要想实现瀑布流效果,我们就要自定义 layout 来实现了。
自定义必须要实现下面的方法

collectionViewContentSize

layoutAttributesForElementsInRect:

layoutAttributesForItemAtIndexPath:

shouldInvalidateLayoutForBoundsChange:

layoutAttributesForSupplementaryViewOfKind:atIndexPath: (如果有 supplementary views 需要实现)

layoutAttributesForDecorationViewOfKind:atIndexPath: (如果有 supports decoration views 需要实现)

具体代码如下:

初始化

ZWWaterFlowLayout *layout = [[ZWWaterFlowLayout alloc] init];
layout.columnsCount = 3;
layout.delegate = self;

UICollectionView *colView = [[UICollectionView alloc] initWithFrame:CGRectMake(0, 0, self.view.width, self.view.height - 64) collectionViewLayout:layout];
colView.backgroundColor = [UIColor grayColor];
[colView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"myCell"];
colView.delegate = self;
colView.dataSource = self;

[self.view addSubview:colView];

ZWWaterFlowLayout:

- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {
return YES;
}

//返回内容尺寸给UICollectionView
- (CGSize)collectionViewContentSize {
    __block NSString *maxColumn = @"0";
    [self.maxYDict enumerateKeysAndObjectsUsingBlock:^(NSString *column, NSNumber *maxY, BOOL *stop) {
        if ([maxY floatValue] > [self.maxYDict[maxColumn] floatValue]) {
            maxColumn = column;
        }
    }];
    return CGSizeMake(0, [self.maxYDict[maxColumn] floatValue] + self.sectionInset.bottom);
}

//每次布局之前的准备
- (void)prepareLayout {
    [super prepareLayout];

    //清空最大的Y值
    for (int i = 0; i < self.columnsCount; i++) {
        NSString *column = [NSString stringWithFormat:@"%d", i];
        self.maxYDict[column] = @(self.sectionInset.top);
    }

    //计算所有cell的属性
    [self.attrsArray removeAllObjects];
    NSInteger count = [self.collectionView numberOfItemsInSection:0];
    for (int i = 0; i<count; i++) {
        UICollectionViewLayoutAttributes *attrs = [self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:i inSection:0]];
        [self.attrsArray addObject:attrs];
    }
}

-(NSArray *)layoutAttributesForElementsInRect:(CGRect )rect {
    return self.attrsArray;
}

//Item的布局属性
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
    //假设最短的那一列的第0列
    __block NSString *minColumn = @"0";
    //找出最短的那一列
    [self.maxYDict enumerateKeysAndObjectsUsingBlock:^(NSString *column, NSNumber *maxY, BOOL *stop) {
        if ([maxY floatValue] < [self.maxYDict[minColumn] floatValue]) {
            minColumn = column;
        }
    }];

    //计算尺寸
    CGFloat width = (self.collectionView.frame.size.width - self.sectionInset.left - self.sectionInset.right - (self.columnsCount - 1) * self.columnMargin)/self.columnsCount;
    CGFloat height = [self.delegate waterflowLayout:self heightForWidth:width atIndexPath:indexPath];;

    //计算位置
    CGFloat x = self.sectionInset.left + (width + self.columnMargin) * [minColumn intValue];
    CGFloat y = [self.maxYDict[minColumn] floatValue] + self.rowMargin;

    //更新这一列的最大Y值
    self.maxYDict[minColumn] = @(y + height);

    //创建属性
    UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
    attrs.frame = CGRectMake(x, y, width, height);
    return attrs;
}

通过 ZWWaterFlowLayoutDelegate 返回的高度的现在用的是随机的,在项目中可以让服务端返回图片的宽和高,我们以宽为基准,来计算高度即可。

- (CGFloat)waterflowLayout:(ZWWaterFlowLayout *)waterflowLayout heightForWidth:(CGFloat)width atIndexPath:(NSIndexPath *)indexPath {
    int from = 50;
    int to = 500;
    CGFloat height = (CGFloat)(from + (arc4random() % (to - from + 1)));
    NSLog(@"height:%f",height);
    return height;
}

运行效果如下:
collectionView

参考链接:

UICollectionViewLayout

Collection View Programming Guide for iOS

UICollectionView