瀑布流小框架

1字数 5206阅读 4899
  • github地址little-waterfall-framework-**

  • 这里把整一个瀑布流小框架的实现思路写出来,今后也可以直接拿着用到项目中

  • 瀑布流

    • 每一个竖线往下,而且长短不一,像瀑布一样往下流,但每条线的长短或者速度都不一样。
  • 假如用九宫格去布局像电商那种衣服之类的图片,是有很多缺点的

    • 1.如果你使用填充,可能会导致图片变形
    • 2.就算你不设置填充,也不可能保证能看到图片的全部
    • 3.如果所有格子的大小都是一样,用户很有可能产生视觉疲劳
    • 瀑布流大小不一样,流水一般,用户看起来会有新鲜感
  • 注意点

    • 我们要展示的图片比较多,就牵扯到循环利用了
    • 图片可以滚动,说明是有一个ScrollView的
    • 我们肯定是要在UIScrollView的身上做上千张上万张图片的循环利用
  • 我们要实现瀑布流,有几种方案:

    • 有三种方案
  • 1.第一种:略显奇葩恶心的做法不推荐

    • 先在上面搞一个ScrollView
    • 再在UIScrollView上搞三个TableView
      • 这样的话,每一�个TableView里面的图片的高度就和其它的TableView里面的图片没有关系了
      • 但是TableView是能滚动的,这样感觉就对不上了,怎么做的呢?
      • 禁止TableView的滚动事件,让后面的ScrollView可以滚动
        • 但这样太麻烦了,还得搞三个数据源点击事件也分得很散,而且你的索引也是有问题的,要跨TableView算索引,很恶心的


  • 第二种方案

    • 往ScrollView添加图片,图片的高度不一样,但是宽度一样
    • 如果我们展示上千张图片,意味着我们要创建上千个这样的View还要把它们排布好,这样的话内存就很大了,所以我们要做循环利用
    • 比如说我们滚到一定位置的时候,有一些家伙不在屏幕里了,我们先把它放进一个集合里,或者是字典里面,或者是数组里面去,把它拿出来先
    • 紧接着我们再往下滚,我们需要一个新的东西,怎么办?我们把前面的拿出来,改变它的高度,并且把里面的图片和文字�换掉就可以了
    • 就像cell,结构不变,但是内容可以换掉,循环利用
    • 但是这种方法也是挺恶心,你需要去不断的判断哪些控件离开了屏幕
    • 这种方法以前有有些人用,但是现在基本没人都不用了


  • 第三种方案

    • 用CollectionView,里面的摆放的都是CollectionViewCell
    • 这样的话就不需要做循环利用了,因为CollectionView会给你做,我们只需要告诉CollectionView这个cell 怎么摆,里面放些什么内容就行了
    • �这样的话你就省了很大的一件事:循环利用(iOS6开始出现)
  • CollectionView默认的流水布局是不能实现我们的瀑布流的功能的,因为流水布局是�保证每一个家伙的高度都是一样的,但是瀑布流不是这样的,它是挑最短的一列去排的,哪一列最短,我就排在它的下面,这样的不停的补齐

  • 所以我们不是继承自CollectionView的流水布局,而是继承自最根本的那个布局Layout,从最基本的布局CollectionViewLayout再写一套布局



  • 基本骨架
  • CollectionView继承自CollectionViewFlowLayout的时候,里面的的cell有一个默认size,会给你按流水布局来排布。你如果是继承自最根本的布局CollectionViewLayout,cell是不知道怎么去排的,需要你自己去排
  • 今后你要继承自CollectionViewLayout这个家伙做事情,一般要实现下面四个方法:
  • 自定义布局--CYWaterFlowLayout继承自CollectionViewLayout
  • 在ViewController.m文件中,先创建好布局
#import "ViewController.h"
#import "CYWaterFlowLayout.h"

@interface ViewController ()<UICollectionViewDataSource>

@end

@implementation ViewController


static NSString * const CYShopId = @"shop";

- (void)viewDidLoad {
    [super viewDidLoad];

    // 创建布局
    CYWaterFlowLayout *layout = [[CYWaterFlowLayout alloc] init];

    // 创建CollectionView
    UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:self.view.bounds collectionViewLayout:layout];
    collectionView.dataSource = self;
    [self.view addSubview:collectionView];

    // 注册
    [collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:CYShopId];
}


#pragma mark - <UICollectionViewDataSource>
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
    return 50;
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:CYShopId forIndexPath:indexPath];

    cell.backgroundColor = [UIColor orangeColor];

    NSInteger tag = 10;
    UILabel *label = (UILabel *)[cell.contentView viewWithTag:tag];
    if (label == nil) {
        label = [[UILabel alloc] init];
        label.tag = tag;
        [cell.contentView addSubview:label];
    }

    label.text = [NSString stringWithFormat:@"%zd", indexPath.item];
    [label sizeToFit];

    return cell;
}
  • 在CYWaterFlowLayout.m文件中

#import "CYWaterFlowLayout.h"


@interface CYWaterFlowLayout()

@end

@implementation CYWaterFlowLayout


- (NSMutableArray *)attrsArray
{
    if (!_attrsArray) {
        _attrsArray = [NSMutableArray array];
    }
    return _attrsArray;
}

/**
 * 初始化
 */
- (void)prepareLayout
{
    [super prepareLayout];
    
    // 开始创建每一个cell对应的布局属性
    NSInteger count = [self.collectionView numberOfItemsInSection:0];
    for (NSInteger i = 0; i < count; i++) {
        // 创建位置
        NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
        // 创建布局属性
        UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
        // 设置布局属性的frame
        attrs.frame = CGRectMake(arc4random(300), arc4random(300), arc4random(300), arc4random(300));
        [self.attrsArray addObject:attrs];
    }
}

/**
 * 决定cell的排布
 */
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
    return self.attrsArray;
}
  • 这里创建布局属性的时候之所以不在layoutAttributesForElementsInRect方法中创建,而在prepareLayout方法中去创建,是因为,如果你是继承自CollectionViewLayout,每次滑动屏幕的时候,layoutAttributesForElementsInRect都会调用,它的调用是动一下调用一次,默认是N频繁(如果你是继承自流水布局CollectionViewFlowLayout不是这种情况,流水布局内部默认有些控制你不能频繁调用它,除非重写shouldInvalidateLayoutForBoundsChange:方法才会频繁调用,这里是有区别的)
  • 而我们的瀑布流算好一次就可以了,因为你算好了一次,再滚回来滚回去都是一样的。而prepareLayout只调用一次,所以我们放在这里去算(所以在这里先把布局属性用个数组装着,再放到layoutAttributesForElementsInRect里面去)。现在虽然滚来滚去layoutAttributesForElementsInRect它还是频繁的调用,但是它返回的是同一个东西,所以我们只算了一次。所以它没变,这样的话就优化了很多。
  • 但是这里还是不够严谨,因为每一次当我们刷新的时候,prepareLayout都会重新调一次,这样的话,你把数组又重新加载进去了,这个数组就越来越大,而且一刷新,到时候,图片又是新的,高度又可能不太一样,那么又得从头到尾再算一遍,既然这样,以前的东西就不要留着了,所以得做一件什么事情?清除--清空以前的数组,清除之前所有的布局属性,不然你的布局属性会越来越大,好一些乱七八糟的东西进去。
  • 而layoutAttributesForItemAtIndexPath是返回indexPath对应cell的布局属性,所以这里得实现以下

#import "CYWaterFlowLayout.h"


@interface CYWaterFlowLayout()

@end

@implementation CYWaterFlowLayout


- (NSMutableArray *)attrsArray
{
    if (!_attrsArray) {
        _attrsArray = [NSMutableArray array];
    }
    return _attrsArray;
}

/**
 * 初始化
 */
- (void)prepareLayout
{
    [super prepareLayout];
    // 清除之前所有的布局属性
    [self.attrsArray removeAllObjects];
    
    // 开始创建每一个cell对应的布局属性
    NSInteger count = [self.collectionView numberOfItemsInSection:0];
    for (NSInteger i = 0; i < count; i++) {
        // 创建位置
        NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
        // 获取indexPath位置cell对应的布局属性
        UICollectionViewLayoutAttributes *attrs = [self layoutAttributesForItemAtIndexPath:indexPath];
        [self.attrsArray addObject:attrs];
    }
}

/**
 * 决定cell的排布
 */
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
    return self.attrsArray;
}

/**
 * 返回indexPath位置cell对应的布局属性
 */
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
    // 创建布局属性
    UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];

    // 设置布局属性的frame
    attrs.frame = CGRectMake(arc4random(300), arc4random(300), arc4random(300), arc4random(300));

    return attrs;
}
  • 现在瀑布流的基本骨架就搭建好了,别的就不必要关注了,你只要关注layoutAttributesForItemAtIndexPath方法中怎么去设置好布局属性的frame。因为瀑布流的核心就是排布方式不一样,你今后就只要去修改它里面的东西就行了
  • 所以写自定义布局或者是写瀑布流最核心的东西还是要回到layoutAttributesForItemAtIndexPath方法,因为你继承自CollectionViewLayout,还是要实现这一些方法,并且最终回到这里做一些最核心事情


  • 核心计算
  • 这里就重点关注layoutAttributesForItemAtIndexPath方法里面的frame对应的X,Y,width,height怎么算了
  • 做好瀑布流我们得知道一下几点:每一个对应cell宽度高度和位置,以及距离屏幕左右间距和上间距,以及每一行间距,每一列间距。
  • 为了方便我们把这些值拿出来,我们先把间距都设置成固定的10,如果有特殊的需求定制,到时候再去修改一下就可以了
  • 而且给一个默认的列数
/** 默认的列数 */
static const NSInteger CYDefaultColumnCount = 3;
/** 每一列之间的间距 */
static const CGFloat CYDefaultColumnMargin = 10;
/** 每一行之间的间距 */
static const CGFloat CYDefaultRowMargin = 10;
/** 边缘间距 */
static const UIEdgeInsets CYDefaultEdgeInsets = {10, 10, 10, 10};
  • 关于这里的边缘间距上下左右的间距要写的话得写四个,变量常量就有点儿多了,所以想到了UIEdgeInsets
static const UIEdgeInsets CYDefaultEdgeInsets = (10, 10, 10, 10);
+  而UIEdgeInsets是一个结构体,我们如果按平时像这么写,这就是一个函数调用了,虽然它只是类名函数。你如果调函数,意味着你在程序运行过程中才会执行,而像我们这种静态变量是�编译器确定的,也就是说编译器编译的时候才会确认这个固定的值,这样就不能执行这个代码了。函数是运行时调用,这样就不行
+  这样的话我们用土方法用一个{}解决,因为它是结构体,相当于把10,10,10,10分别赋值给四个属性,所以今后要写静态变量的结构体,这也是一种方法
  • 宽度好算,现在要开始去确定每一个cell的X和Y值,以及它们的高度,目前高度是无法确定的就先写死,先重点算一下cell的X和Y值
  • 算X值就�求出列号就可以了,因为每一列的X值是相同的,所以重点你是找出最短的哪一列,所有就可以把Y值求出来了
    // collectionView的宽度
    CGFloat collectionViewW = self.collectionView.frame.size.width;

    // 设置布局属性的frame
    CGFloat w = (collectionViewW - CYDefaultEdgeInsets.left - CYDefaultEdgeInsets.right - (CYDefaultColumnCount - 1) * CYDefaultColumnMargin) / CYDefaultColumnCount;
    CGFloat h = 50 + arc4random_uniform(100);
  • 但是每一列的高度都在变化,而且列数你也不确定,为了扩展性,所以也要搞个数组存起来,第0列最大的Y值,第1列最大的Y值,第2列最大的Y值,找出最大Y值最小的一列,为了好理解,�这里先存放所有列的当前高度
/** 存放所有列的当前高度 */
@property (nonatomic, strong) NSMutableArray *columnHeights;
@end

@implementation CYWaterFlowLayout

- (NSMutableArray *)columnHeights
{
    if (!_columnHeights) {
        _columnHeights = [NSMutableArray array];
    }
    return _columnHeights;
}
  • 找出高度最小的一列,所以要通过遍历,遍历之后就能找到最短的一列。遍历可以通过block或者for循环
    • 先用block遍历
    • 问题是数组里面只能放对象,所以block里面遍历最后取出来的时候要包装为对象
        //     找出高度最短的那一列
        __block NSInteger destColumn = 0;
        //  目标那一列
        __block CGFloat minColumnHeight = MAXFLOAT;
        //  先保证minColumnHeight是最大的才能保证第一个家伙比他小
        //  block要改外面的变量,要加一个修饰符__block
        [self.columnHeights enumerateObjectsUsingBlock:^(NSNumber *columnHeightNumber, NSUInteger idx, BOOL *stop) {
            CGFloat columnHeight = columnHeightNumber.doubleValue;
            if (minColumnHeight > columnHeight) {
                minColumnHeight = columnHeight;
                destColumn = idx;
                //  目标列
            }
        }];
  • for循环遍历
```objc
// 找出高度最短的那一列
NSInteger destColumn = 0;
CGFloat minColumnHeight = [self.columnHeights[0] doubleValue];
 //  第0个值我已经取出来了,我们就可以从第1个开始和0进行比较了
 //  这里虽然是小小的优化少了一次遍历,但是不能小瞧少这一次的遍历,因为今后这个方法调得很频繁
 //  这也是这里用for循环而不用block的原因,我们能控制从哪儿开始遍历
for (NSInteger i = 1; i < CYDefaultColumnCount; i++) {
    // 取得第i列的高度
    CGFloat columnHeight = [self.columnHeights[i] doubleValue];

    if (minColumnHeight > columnHeight) {
        minColumnHeight = columnHeight;
        destColumn = i;
    }
}
-  我们已经找到了最短那列的高度了,所以X和Y值就可以确定了

```objc
    CGFloat x = CYDefaultEdgeInsets.left + destColumn * (w + CYDefaultColumnMargin);
    CGFloat y = minColumnHeight + CYDefaultRowMargin;
    attrs.frame = CGRectMake(x, y, w, h);
  • 但是还得做最后一件事情,你在这里加了一个新的列,所以我们还得更新一下高度
    // 更新最短那列的高度
    self.columnHeights[destColumn] = @(CGRectGetMaxY(attrs.frame));
  • 但是这里还有问题:0索引越界空数组,因为我们第一次访问那个数组,是一个空的数组,什么都没有。
  • 所以我们在得在prepareLayout再做一次初始化:每次刷新,得把这个数组中存的高度都得删掉,从0开始再算一遍,所以prepareLayout得清除以前计算的高度,因为我们一刷新,可能里面的cell的高度都是不一样的,得重新算一遍,所以清除以前的高度,这是最严谨的做法
[super prepareLayout];

    // 清除以前计算的所有高度
    [self.columnHeights removeAllObjects];
    for (NSInteger i = 0; i < CYDefaultColumnCount; i++) {
        [self.columnHeights addObject:@(CYDefaultEdgeInsets.top)];
    }
  • 判断是不是第一行

    CGFloat x = CYDefaultEdgeInsets.left + destColumn * (w + CYDefaultColumnMargin);
    CGFloat y = minColumnHeight;
    if (y != CYDefaultEdgeInsets.top) {
        y += CYDefaultRowMargin;
    }
    attrs.frame = CGRectMake(x, y, w, h);

    // 更新最短那列的高度
    self.columnHeights[destColumn] = @(CGRectGetMaxY(attrs.frame));
  • 算出最长的那一列,去更新内容的高度
- (CGSize)collectionViewContentSize
{
    CGFloat maxColumnHeight = [self.columnHeights[0] doubleValue];
    for (NSInteger i = 1; i < CYDefaultColumnCount; i++) {
        // 取得第i列的高度
        CGFloat columnHeight = [self.columnHeights[i] doubleValue];

        if (maxColumnHeight < columnHeight) {
            maxColumnHeight = columnHeight;
        }
    }
    return CGSizeMake(0, maxColumnHeight + CYDefaultEdgeInsets.bottom);
}

  • 所以在CYWaterFlowLayout.m文件中
#import "CYWaterFlowLayout.h"

/** 默认的列数 */
static const NSInteger CYDefaultColumnCount = 3;
/** 每一列之间的间距 */
static const CGFloat CYDefaultColumnMargin = 10;
/** 每一行之间的间距 */
static const CGFloat CYDefaultRowMargin = 10;
/** 边缘间距 */
static const UIEdgeInsets CYDefaultEdgeInsets = {10, 10, 10, 10};

@interface CYWaterFlowLayout()
/** 存放所有cell的布局属性 */
@property (nonatomic, strong) NSMutableArray *attrsArray;
/** 存放所有列的当前高度 */
@property (nonatomic, strong) NSMutableArray *columnHeights;
@end

@implementation CYWaterFlowLayout

- (NSMutableArray *)columnHeights
{
    if (!_columnHeights) {
        _columnHeights = [NSMutableArray array];
    }
    return _columnHeights;
}

- (NSMutableArray *)attrsArray
{
    if (!_attrsArray) {
        _attrsArray = [NSMutableArray array];
    }
    return _attrsArray;
}

/**
 * 初始化
 */
- (void)prepareLayout
{
    [super prepareLayout];

    // 清除以前计算的所有高度
    [self.columnHeights removeAllObjects];
    for (NSInteger i = 0; i < CYDefaultColumnCount; i++) {
        [self.columnHeights addObject:@(CYDefaultEdgeInsets.top)];
    }

    // 清除之前所有的布局属性
    [self.attrsArray removeAllObjects];
    // 开始创建每一个cell对应的布局属性
    NSInteger count = [self.collectionView numberOfItemsInSection:0];
    for (NSInteger i = 0; i < count; i++) {
        // 创建位置
        NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
        // 获取indexPath位置cell对应的布局属性
        UICollectionViewLayoutAttributes *attrs = [self layoutAttributesForItemAtIndexPath:indexPath];
        [self.attrsArray addObject:attrs];
    }
}

/**
 * 决定cell的排布
 */
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
    return self.attrsArray;
}

/**
 * 返回indexPath位置cell对应的布局属性
 */
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
    // 创建布局属性
    UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];

    // collectionView的宽度
    CGFloat collectionViewW = self.collectionView.frame.size.width;

    // 设置布局属性的frame
    CGFloat w = (collectionViewW - CYDefaultEdgeInsets.left - CYDefaultEdgeInsets.right - (CYDefaultColumnCount - 1) * CYDefaultColumnMargin) / CYDefaultColumnCount;
    CGFloat h = 50 + arc4random_uniform(100);

    // 找出高度最短的那一列
    NSInteger destColumn = 0;
    CGFloat minColumnHeight = [self.columnHeights[0] doubleValue];
    for (NSInteger i = 1; i < CYDefaultColumnCount; i++) {
        // 取得第i列的高度
        CGFloat columnHeight = [self.columnHeights[i] doubleValue];

        if (minColumnHeight > columnHeight) {
            minColumnHeight = columnHeight;
            destColumn = i;
        }
    }

    CGFloat x = CYDefaultEdgeInsets.left + destColumn * (w + CYDefaultColumnMargin);
    CGFloat y = minColumnHeight;
    if (y != CYDefaultEdgeInsets.top) {
        y += CYDefaultRowMargin;
    }
    attrs.frame = CGRectMake(x, y, w, h);

    // 更新最短那列的高度
    self.columnHeights[destColumn] = @(CGRectGetMaxY(attrs.frame));

    return attrs;
}

- (CGSize)collectionViewContentSize
{
    CGFloat maxColumnHeight = [self.columnHeights[0] doubleValue];
    for (NSInteger i = 1; i < CYDefaultColumnCount; i++) {
        // 取得第i列的高度
        CGFloat columnHeight = [self.columnHeights[i] doubleValue];

        if (maxColumnHeight < columnHeight) {
            maxColumnHeight = columnHeight;
        }
    }
    return CGSizeMake(0, maxColumnHeight + CYDefaultEdgeInsets.bottom);
}

@end
  • 这个瀑布流就基本实现了,特殊的需求或者是定制的内容,你去修改那些间距或者行数就行了
  • �严谨处理:记得清除以前的数据,因为假如你做了一个下拉刷新,请求数据,一刷新就会重新调用prepareLayout,你的那些高度就会变了,不清除以前的数据,数组就会越来越大,就会出错,这也是瀑布流的核心。最关键最重要的的当然就是去找最短的那一列了
  • 当然这里的高度还是得由外界决定,也就是服务器返回的数据决定:比如说你的功能要展示的是衣服,就应该由衣服的高度决定,展示的是新闻,就应该由新闻的高度决定。总之,你的高度是由数据决定的。


  • 显示数据
  • 我们上面做的是有缺陷的,我们图片的显示应该有完美的宽高比,它的宽度高度是应该由图片决定的,高度应该释放出来,而不是由你自己决定,应该由商品决定
  • 商品模型数据先弄出来,这个很简单, 主要核心在接口设计
  • plist文件中有商品模型数据,用模型去接收一下
  • 在CYShop.h文件中存放模型数据
#import <UIKit/UIKit.h>

@interface CYShop : NSObject
@property (nonatomic, assign) CGFloat w;
@property (nonatomic, assign) CGFloat h;
@property (nonatomic, copy) NSString *img;
@property (nonatomic, copy) NSString *price;
@end
  • 在CYShop.m文件中
#import "CYShop.h"

@implementation CYShop

@end
  • 在CYShopCell.h文件中
#import <UIKit/UIKit.h>
@class CYShop;
@interface CYShopCell : UICollectionViewCell
@property (nonatomic, strong) CYShop *shop;
@end
  • 在CYShopCell.m文件中
#import "CYShopCell.h"
#import "CYShop.h"
#import "UIImageView+WebCache.h"

@interface CYShopCell()
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@property (weak, nonatomic) IBOutlet UILabel *priceLabel;
@end

@implementation CYShopCell

- (void)setShop:(CYShop *)shop
{
    _shop = shop;

    // 1.图片
    [self.imageView sd_setImageWithURL:[NSURL URLWithString:shop.img] placeholderImage:[UIImage imageNamed:@"loading"]];

    // 2.价格
    self.priceLabel.text = shop.price;
}
@end
  • CYShopCell.xib




  • 在控制器中加载数据
  • 因为要不断往后面拼接数据,所以搞一个可变数组数组
/** 所有的商品数据 */
@property (nonatomic, strong) NSMutableArray *shops;
@property (nonatomic, weak) UICollectionView *collectionView;
  • 加载新商品数据从plist文件中加载
  • 整个加载数据:具体步骤,我给了注释
  • 在viewController.m文件中
#import "ViewController.h"
#import "CYWaterFlowLayout.h"
#import "CYShop.h"
#import "MJExtension.h"
#import "MJRefresh.h"
#import "CYShopCell.h"

@interface ViewController () <UICollectionViewDataSource>
/** 所有的商品数据 */
@property (nonatomic, strong) NSMutableArray *shops;

@property (nonatomic, weak) UICollectionView *collectionView;
@end

@implementation ViewController

- (NSMutableArray *)shops
{
    if (!_shops) {
        _shops = [NSMutableArray array];
    }
    return _shops;
}

static NSString * const CYShopId = @"shop";

- (void)viewDidLoad {
    [super viewDidLoad];

    [self setupLayout];

    [self setupRefresh];
}

- (void)setupRefresh
{
    //  下拉刷新
    self.collectionView.mj_header = [MJRefreshNormalHeader headerWithRefreshingTarget:self refreshingAction:@selector(loadNewShops)];
    //  一进入就刷新数据
    [self.collectionView.mj_header beginRefreshing];
    //  上拉自动刷新
    self.collectionView.mj_footer = [MJRefreshAutoNormalFooter footerWithRefreshingTarget:self refreshingAction:@selector(loadMoreShops)];
    //  结束刷新
    self.collectionView.mj_footer.hidden = YES;
}

//  下拉刷新数据
- (void)loadNewShops
{
    //  模拟网络请求延时加载
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        //  字典转模型,从plist文件中加载商品数据模型数组
        NSArray *shops = [CYShop mj_objectArrayWithFilename:@"1.plist"];
        //  每次刷新都要重新加载数据,所以先移除,再重新加载数据
        [self.shops removeAllObjects];
        [self.shops addObjectsFromArray:shops];

        // 刷新数据
        [self.collectionView reloadData];
        //  数据回来后结束刷新
        [self.collectionView.mj_footer endRefreshing];
    });
}

//  上拉刷新数据
- (void)loadMoreShops
{
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSArray *shops = [CYShop mj_objectArrayWithFilename:@"1.plist"];
        [self.shops addObjectsFromArray:shops];

        // 刷新数据
        [self.collectionView reloadData];

        [self.collectionView.mj_footer endRefreshing];
    });
}

- (void)setupLayout
{
    // 创建布局
    CYWaterFlowLayout *layout = [[CYWaterFlowLayout alloc] init];

    // 创建CollectionView
    UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:self.view.bounds collectionViewLayout:layout];
    collectionView.backgroundColor = [UIColor whiteColor];
    collectionView.dataSource = self;
    [self.view addSubview:collectionView];

    // 注册(从Xib加载)
    [collectionView registerNib:[UINib nibWithNibName:NSStringFromClass([CYShopCell class]) bundle:nil] forCellWithReuseIdentifier:CYShopId];

    self.collectionView = collectionView;
}

#pragma mark - <UICollectionViewDataSource>
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
    self.collectionView.mj_footer.hidden = self.shops.count == 0;
    return self.shops.count;
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    CYShopCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:CYShopId forIndexPath:indexPath];

    cell.shop = self.shops[indexPath.item];

    return cell;
}
  
@end
  • 现在基本格局搭建好了,接下来就是由我们模型的宽度高度去决定每个cell的宽度高度

  • 完善接口
  • �高度取决我们的商品,和商品有关,意味着我们得知道对应cell位置模型的宽高比才能算出高度来
  • 我这里要做的是一个小框架,今后还可以用来展示别的内容,所以不能严重依赖项目模型,要有扩展性,那我要怎么做呢?
  • 思想:
    • 根据tableView的原理,tableView怎么做的,tableView的cell的高度由什么决定的?代理。为什么tableView要设计为代理决定呢?tableView保证了什么数据都能显示,�为了保证这一点,那么就由显示数据的那一方来告诉它你的高度是多少。那么我的瀑布流也想什么数据都能显示,就得由显示数据那一方决定,比如说控制器作为我代理来告诉我我的高度是多少。
  • 我平时写代码的时候会想一想苹果为什么这么设计,为什么要搞数据源为什么要搞代理呢?它肯定是有道理的,你也会发现tableView是很牛逼的,它什么数据都能显示(设置界面,通讯录,淘宝等等任何一个应用都有它的身影)同一个控键,任何应用都会有它,这说明它是通用的,它为什么能通用呢?它里面展示内容的cell是由数据源告诉它的,它的数据都是由别人去告诉它的,所以换任何人的任何数据都能显示。所以我这里设计瀑布流也是这样,应该由别人来告诉我怎么做,这样才可以丰富多彩。不管是谁告诉我都可以,当然有个条件你得遵守我的协议,按照内容方式告诉我就行了。比如说你成为tableView的数据源,实现我那个数据源方法,在那个方法里返回东西给我,我就帮你显示出来,究竟显示什么我不管,总之你返回给我就对了
  • 接下来弄个代理出来
    • 类型先声明一下
    @class CYWaterFlowLayout;
    
    +  代理是弱引用,用weak,加一个id,协议以自己的名字开头
    ```objc
@interface CYWaterFlowLayout : UICollectionViewLayout
/** 代理 */
@property (nonatomic, weak) id<CYWaterFlowLayoutDelegate> delegate;
+  这个方法里不再用optional了,optional用的时候那些方法是可有可无的,假如你监听我内部实现,可以监听也可以不监听。所以这里我用 required,得强制实现,因为你不告诉我高度,我没法显示,现在的级别不一样了,你作为代理,你必须告诉我,不告诉我,我就不帮你显示了,我直接报错。
+  而且这个代理方法必须要有个返回值,,返回每个cell对应的高度
+  这里的代理方法也是参考官方tableView代理去写的         
```objc

@required

  • (CGFloat)waterflowLayout:(CYWaterFlowLayout *)waterflowLayout heightForItemAtIndex:(NSUInteger)index itemWidth:(CGFloat)itemWidth;
    // 将我item的宽度传给代理,代理通过cell宽度就可以通过图片的宽高比去算出cell的高度返回给我
    +  让控制器成为我的代理
     ```objc
      @interface ViewController () <UICollectionViewDataSource, CYWaterFlowLayoutDelegate>

    layout.delegate = self;
  • 在CYWaterFlowLayout.h文件中

#import <UIKit/UIKit.h>

@class CYWaterFlowLayout;

@protocol CYWaterFlowLayoutDelegate <NSObject>
@required
- (CGFloat)waterflowLayout:(CYWaterFlowLayout *)waterflowLayout heightForItemAtIndex:(NSUInteger)index itemWidth:(CGFloat)itemWidth;
@end

@interface CYWaterFlowLayout : UICollectionViewLayout
/** 代理 */
@property (nonatomic, weak) id<CYWaterFlowLayoutDelegate> delegate;
@end
  • 在viewController.m文件中实现代理方法
#pragma mark - <CYWaterflowLayoutDelegate>
- (CGFloat)waterflowLayout:(CYWaterFlowLayout *)waterflowLayout heightForItemAtIndex:(NSUInteger)index itemWidth:(CGFloat)itemWidth
{
    CYShop *shop = self.shops[index];

    return itemWidth * shop.h / shop.w;
}
  • 这段和模型相关的代码就放在控制器了,这样就和我们的瀑布流小框架没有一点关系了
  • 我管你是谁显示的是商品,图片还是什么,我只向你索要高度,究竟显示什么,我不管,只要遵守协议,给我数据就可以了
    // 设置布局属性的frame
    CGFloat w = (collectionViewW - self.edgeInsets.left - self.edgeInsets.right - (self.columnCount - 1) * self.columnMargin) / self.columnCount;
    CGFloat h = [self.delegate waterflowLayout:self heightForItemAtIndex:indexPath.item itemWidth:w];
  • 现在就做到了通用性了。这个瀑布流现在和商品一点关系都没有了。你就看这个文件也看不出来它今后显示的是什么东西。你不知道它以后显示的是商品还是新闻还是大照片之类的东西。现在只要别人返回一个高度给我,不用管别人是啥,是新闻,图片或者是什么也好,框架里的东西不用改。

  • 但是这里�还是设计得不够好,扩展性还不够强,因为那些列数,间距不应该写死,也是应该通过一个代理方法去控制的。它应该交给别人去设置,就算不设置,我也要给个默认值。
  • 所以还提供一些代理方法,但是这次用optional,不用required。因为就算你不设置,我还有默认值。但是你要调用optional的时候,要判断
  • 参考tableView
@optional
- (CGFloat)columnCountInWaterflowLayout:(CYWaterFlowLayout *)waterflowLayout;
- (CGFloat)columnMarginInWaterflowLayout:(CYWaterFlowLayout *)waterflowLayout;
- (CGFloat)rowMarginInWaterflowLayout:(CYWaterFlowLayout *)waterflowLayout;
- (UIEdgeInsets)edgeInsetsInWaterflowLayout:(CYWaterFlowLayout *)waterflowLayout;
  • 这样的话,优先从代理获取,代理没有就用默认的
// 声明一下,点语法就能调用了
- (CGFloat)rowMargin;
- (CGFloat)columnMargin;
- (NSInteger)columnCount;
- (UIEdgeInsets)edgeInsets;
@end

@implementation CYWaterFlowLayout

#pragma mark - 常见数据处理
- (CGFloat)rowMargin
{
    if ([self.delegate respondsToSelector:@selector(rowMarginInWaterflowLayout:)]) {
        return [self.delegate rowMarginInWaterflowLayout:self];
    } else {
        return CYDefaultRowMargin;
    }
}

- (CGFloat)columnMargin
{
    if ([self.delegate respondsToSelector:@selector(columnMarginInWaterflowLayout:)]) {
        return [self.delegate columnMarginInWaterflowLayout:self];
    } else {
        return CYDefaultColumnMargin;
    }
}

- (NSInteger)columnCount
{
    if ([self.delegate respondsToSelector:@selector(columnCountInWaterflowLayout:)]) {
        return [self.delegate columnCountInWaterflowLayout:self];
    } else {
        return CYDefaultColumnCount;
    }
}

- (UIEdgeInsets)edgeInsets
{
    if ([self.delegate respondsToSelector:@selector(edgeInsetsInWaterflowLayout:)]) {
        return [self.delegate edgeInsetsInWaterflowLayout:self];
    } else {
        return CYDefaultEdgeInsets;
    }
}
  • 现在,你完全可以自愿的选择性的去实现它告诉我,也就是说来到我们控制器,你作为我瀑布流的代理,你是有权利和资格决定我瀑布流控件里面是怎么一回事的,比如说里面的高度
#pragma mark - <CYWaterflowLayoutDelegate>
- (CGFloat)waterflowLayout:(CYWaterflowLayout *)waterflowLayout heightForItemAtIndex:(NSUInteger)index itemWidth:(CGFloat)itemWidth
{
    CYShop *shop = self.shops[index];

    return itemWidth * shop.h / shop.w;
}

- (CGFloat)rowMarginInWaterflowLayout:(CYWaterflowLayout *)waterflowLayout
{
    return 20;
}

- (CGFloat)columnCountInWaterflowLayout:(CYWaterflowLayout *)waterflowLayout
{
    if (self.shops.count <= 50) return 2;
    return 3;
}

- (UIEdgeInsets)edgeInsetsInWaterflowLayout:(CYWaterflowLayout *)waterflowLayout
{
    return UIEdgeInsetsMake(10, 20, 30, 100);
}

  • 现在就可以做到根本不用动瀑布流框架的源代码,在外面就可以�控制到今后怎么显示,也就是说我只要遵守你的协议,实现你的方法,就可以告诉你你里面应该怎么做,比如说:你可以就显示2列,让edgeInsetsInWaterflowLayout的间距也改变成自己自定义的值
  • 其实可以认为这是一个纯正的MVC,比如说你就把它当做一个控件,你这个控件里面怎么排布,你的控件里面有多少列,完全是由控制器决定的,只要外面动一下,你里面就跟着改。像tableView就是一个完全的MVC,tableView里面显示什么是由外界决定的,外面的模型改了,外面的数据改了,它里面跟着改

  • 假如外面显示数据比较多,比如说1000条的时候,意味着我们要for循环1000次,把所有东西给算完。这个数组就可能要装一千个对象,先从内存的角度,这个是无关紧要的。因为当我们在做移动开发的时候,是什么占内存呢?
    • 占内存最关键的是下载的图片,音频和视频。而数组中装的attrs只是一个普通的对象,可能只有几B,我们创建1000个对象,也就类似于创建了一千个NSObject,只是一个普通的对象,所以从内存的角度,没什么担忧,唯一的担忧就可能是数据的问题,比如说要遍历一千次,就可能会导致线程会有那么一段时间稍微的阻塞,当然这个东西不算太耗时
    • 有一些操作是比桀骜耗时的:比如说大家常见的字符串拼接,你要不断的拼接,拼接意味着你要产生一个新的字符串。还有NSLog,也是比较耗时的,也相当于每次都拼接出一个新的字符串出来
    • 如果大家还是觉得耗时的话可以把它放在一个子线程中去算,然后再搞回来。或者你也可以去将它滚到什么位置就�只算那个范围里面的内容,但是你还是要把前面的先算出来。反正其实在这里,还是不算耗时的,大家不用担心,而且像我们的瀑布流数据也不会那么夸张,成千上万条,一般你拖到一定程度也会停住,或者说可能会刷新
  • 这个瀑布流小框架对大家还是有好处的,也考虑到了很多东西,你可以直接把它拽走,用到你可能需要的项目中。只要你成为我的代理,实现这些方法,就可以告诉我高度多少,间距多少,里面的数据取决于你的cell,和CollectionView有关。你究竟想要在cell里面显示什么东西和我的瀑布流是没有关系的,我这里做的是自定义布局,只和布局有关,也就意味着你的内容和我一点关系都没有,可以放心大胆的用到项目中去
  • 已经上传到github:****little-waterfall-framework-
  • 对你有帮助,可以给颗星※

  • 补充一点,可能会有朋友会认为那些高度,间距可以直接通过用属性来传值,更加简单。这种方式灵活性不是很强,有些麻烦,还得实现一个方法,还得返回东西。
  • 那样是可以,但是你在使用的时候,可能就会在一个地方�传值比如:rowMargin = 10,在另外一个地方又传了一个rowMargin = 30。意味着你内部的东西又得重新算一遍。如果提供属性传值的接口去用,就可以乱改了
  • 但是这里提供方法就不一样了,调用时刻是你决定的,这些方法是你决定什么时候调就什么时候调用,还可以做一些定制化的内容:
- (CGFloat)rowMarginInWaterflowLayout:(CYWaterFlowLayout *)waterflowLayout
{ 
      return 20;
}
- (CGFloat)columnCountInWaterflowLayout:(CYWaterFlowLayout *)waterflowLayout
{
     if (self.shops.count <= 50) return 2; 
     return 3;
     //  当现实内容少于50个的时候为两列,大于50个的时候为3列
}
- (UIEdgeInsets)edgeInsetsInWaterflowLayout:(CYWaterFlowLayout *)waterflowLayout
{
     return UIEdgeInsetsMake(10, 20, 30, 100);
}






  • 最后
  • 在CYWaterFlowLayout.h文件中
#import <UIKit/UIKit.h>

@class CYWaterFlowLayout;

@protocol CYWaterFlowLayoutDelegate <NSObject>
@required
- (CGFloat)waterflowLayout:(CYWaterFlowLayout *)waterflowLayout heightForItemAtIndex:(NSUInteger)index itemWidth:(CGFloat)itemWidth;

@optional
- (CGFloat)columnCountInWaterflowLayout:(CYWaterFlowLayout *)waterflowLayout;
- (CGFloat)columnMarginInWaterflowLayout:(CYWaterFlowLayout *)waterflowLayout;
- (CGFloat)rowMarginInWaterflowLayout:(CYWaterFlowLayout *)waterflowLayout;
- (UIEdgeInsets)edgeInsetsInWaterflowLayout:(CYWaterFlowLayout *)waterflowLayout;
@end

@interface CYWaterFlowLayout : UICollectionViewLayout
/** 代理 */
@property (nonatomic, weak) id<CYWaterFlowLayoutDelegate> delegate;
@end
  • 在CYWaterFlowLayout.m文件中
#import "CYWaterFlowLayout.h"

/** 默认的列数 */
static const NSInteger CYDefaultColumnCount = 3;
/** 每一列之间的间距 */
static const CGFloat CYDefaultColumnMargin = 10;
/** 每一行之间的间距 */
static const CGFloat CYDefaultRowMargin = 10;
/** 边缘间距 */
static const UIEdgeInsets CYDefaultEdgeInsets = {10, 10, 10, 10};

@interface CYWaterFlowLayout()
/** 存放所有cell的布局属性 */
@property (nonatomic, strong) NSMutableArray *attrsArray;
/** 存放所有列的当前高度 */
@property (nonatomic, strong) NSMutableArray *columnHeights;
/** 内容的高度 */
@property (nonatomic, assign) CGFloat contentHeight;

// 声明一下,点语法就能调用了
- (CGFloat)rowMargin;
- (CGFloat)columnMargin;
- (NSInteger)columnCount;
- (UIEdgeInsets)edgeInsets;
@end

@implementation CYWaterFlowLayout

#pragma mark - 常见数据处理
- (CGFloat)rowMargin
{
    if ([self.delegate respondsToSelector:@selector(rowMarginInWaterflowLayout:)]) {
        return [self.delegate rowMarginInWaterflowLayout:self];
    } else {
        return CYDefaultRowMargin;
    }
}

- (CGFloat)columnMargin
{
    if ([self.delegate respondsToSelector:@selector(columnMarginInWaterflowLayout:)]) {
        return [self.delegate columnMarginInWaterflowLayout:self];
    } else {
        return CYDefaultColumnMargin;
    }
}

- (NSInteger)columnCount
{
    if ([self.delegate respondsToSelector:@selector(columnCountInWaterflowLayout:)]) {
        return [self.delegate columnCountInWaterflowLayout:self];
    } else {
        return CYDefaultColumnCount;
    }
}

- (UIEdgeInsets)edgeInsets
{
    if ([self.delegate respondsToSelector:@selector(edgeInsetsInWaterflowLayout:)]) {
        return [self.delegate edgeInsetsInWaterflowLayout:self];
    } else {
        return CYDefaultEdgeInsets;
    }
}

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

- (NSMutableArray *)attrsArray
{
    if (!_attrsArray) {
        _attrsArray = [NSMutableArray array];
    }
    return _attrsArray;
}

/**
 * 初始化
 */
- (void)prepareLayout
{
    [super prepareLayout];

    self.contentHeight = 0;

    // 清除以前计算的所有高度
    [self.columnHeights removeAllObjects];
    for (NSInteger i = 0; i < self.columnCount; i++) {
        [self.columnHeights addObject:@(self.edgeInsets.top)];
    }

    // 清除之前所有的布局属性
    [self.attrsArray removeAllObjects];
    // 开始创建每一个cell对应的布局属性
    NSInteger count = [self.collectionView numberOfItemsInSection:0];
    for (NSInteger i = 0; i < count; i++) {
        // 创建位置
        NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
        // 获取indexPath位置cell对应的布局属性
        UICollectionViewLayoutAttributes *attrs = [self layoutAttributesForItemAtIndexPath:indexPath];
        [self.attrsArray addObject:attrs];
    }
}

/**
 * 决定cell的排布
 */
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
    return self.attrsArray;
}

/**
 * 返回indexPath位置cell对应的布局属性
 */
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
    // 创建布局属性
    UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];

    // collectionView的宽度
    CGFloat collectionViewW = self.collectionView.frame.size.width;

    // 设置布局属性的frame
    CGFloat w = (collectionViewW - self.edgeInsets.left - self.edgeInsets.right - (self.columnCount - 1) * self.columnMargin) / self.columnCount;
    CGFloat h = [self.delegate waterflowLayout:self heightForItemAtIndex:indexPath.item itemWidth:w];

    // 找出高度最短的那一列
    NSInteger destColumn = 0;
    CGFloat minColumnHeight = [self.columnHeights[0] doubleValue];
    for (NSInteger i = 1; i < self.columnCount; i++) {
        // 取得第i列的高度
        CGFloat columnHeight = [self.columnHeights[i] doubleValue];

        if (minColumnHeight > columnHeight) {
            minColumnHeight = columnHeight;
            destColumn = i;
        }
    }

    CGFloat x = self.edgeInsets.left + destColumn * (w + self.columnMargin);
    CGFloat y = minColumnHeight;
    if (y != self.edgeInsets.top) {
        y += self.rowMargin;
    }
    attrs.frame = CGRectMake(x, y, w, h);

    // 更新最短那列的高度
    self.columnHeights[destColumn] = @(CGRectGetMaxY(attrs.frame));

    // 记录内容的高度
    CGFloat columnHeight = [self.columnHeights[destColumn] doubleValue];
    if (self.contentHeight < columnHeight) {
        self.contentHeight = columnHeight;
    }
    return attrs;
}

- (CGSize)collectionViewContentSize
{
    //    CGFloat maxColumnHeight = [self.columnHeights[0] doubleValue];
    //    for (NSInteger i = 1; i < self.columnCount; i++) {
    //        // 取得第i列的高度
    //        CGFloat columnHeight = [self.columnHeights[i] doubleValue];
    //
    //        if (maxColumnHeight < columnHeight) {
    //            maxColumnHeight = columnHeight;
    //        }
    //    }
    return CGSizeMake(0, self.contentHeight + self.edgeInsets.bottom);
}

@end
  • 在viewController.m文件中
#import "ViewController.h"
#import "CYWaterFlowLayout.h"
#import "CYShop.h"
#import "MJExtension.h"
#import "MJRefresh.h"
#import "CYShopCell.h"

@interface ViewController () <UICollectionViewDataSource, CYWaterFlowLayoutDelegate>
/** 所有的商品数据 */
@property (nonatomic, strong) NSMutableArray *shops;

@property (nonatomic, weak) UICollectionView *collectionView;
@end

@implementation ViewController

- (NSMutableArray *)shops
{
    if (!_shops) {
        _shops = [NSMutableArray array];
    }
    return _shops;
}

static NSString * const CYShopId = @"shop";

- (void)viewDidLoad {
    [super viewDidLoad];

    [self setupLayout];

    [self setupRefresh];
}

- (void)setupRefresh
{
    self.collectionView.mj_header = [MJRefreshNormalHeader headerWithRefreshingTarget:self refreshingAction:@selector(loadNewShops)];
    [self.collectionView.mj_header beginRefreshing];

    self.collectionView.mj_footer = [MJRefreshAutoNormalFooter footerWithRefreshingTarget:self refreshingAction:@selector(loadMoreShops)];
    self.collectionView.mj_footer.hidden = YES;
}

- (void)loadNewShops
{
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSArray *shops = [CYShop mj_objectArrayWithFilename:@"1.plist"];
        [self.shops removeAllObjects];
        [self.shops addObjectsFromArray:shops];

        // 刷新数据
        [self.collectionView reloadData];

        [self.collectionView.mj_footer endRefreshing];
    });
}

- (void)loadMoreShops
{
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSArray *shops = [CYShop mj_objectArrayWithFilename:@"1.plist"];
        [self.shops addObjectsFromArray:shops];

        // 刷新数据
        [self.collectionView reloadData];

        [self.collectionView.mj_footer endRefreshing];
    });
}

- (void)setupLayout
{
    // 创建布局
    CYWaterFlowLayout *layout = [[CYWaterFlowLayout alloc] init];
    layout.delegate = self;

    // 创建CollectionView
    UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:self.view.bounds collectionViewLayout:layout];
    collectionView.backgroundColor = [UIColor whiteColor];
    collectionView.dataSource = self;
    [self.view addSubview:collectionView];

    // 注册
    [collectionView registerNib:[UINib nibWithNibName:NSStringFromClass([CYShopCell class]) bundle:nil] forCellWithReuseIdentifier:CYShopId];

    self.collectionView = collectionView;
}

#pragma mark - <UICollectionViewDataSource>
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
    self.collectionView.mj_footer.hidden = self.shops.count == 0;
    return self.shops.count;
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    CYShopCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:CYShopId forIndexPath:indexPath];

    cell.shop = self.shops[indexPath.item];

    return cell;
}

#pragma mark - <CYWaterflowLayoutDelegate>
- (CGFloat)waterflowLayout:(CYWaterFlowLayout *)waterflowLayout heightForItemAtIndex:(NSUInteger)index itemWidth:(CGFloat)itemWidth
{
    CYShop *shop = self.shops[index];

    return itemWidth * shop.h / shop.w;
}

- (CGFloat)rowMarginInWaterflowLayout:(CYWaterFlowLayout *)waterflowLayout
{
    return 20;
}

- (CGFloat)columnCountInWaterflowLayout:(CYWaterFlowLayout *)waterflowLayout
{
    if (self.shops.count <= 50) return 2;
    return 3;
}

- (UIEdgeInsets)edgeInsetsInWaterflowLayout:(CYWaterFlowLayout *)waterflowLayout
{
    return UIEdgeInsetsMake(10, 10, 10, 10);
}

@end

推荐阅读更多精彩内容