瀑布流小框架

作者: Tuberose | 来源:发表于2015-11-18 02:30 被阅读4013次
    • 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
    

    相关文章

      网友评论

      • 5d6b0fe4e749: :+1: :+1: :+1: 学习了
      • 大墙66370:有瑕疵啊,你每次跟新过高度之后对吧直接 把刚跟新的那一列的高度给 你的contentHeight了..
        这样不太合适吧..
        首先contentHeight肯定是这三列最高的高度对吧
        你直接吧刚更新的那一列高度给contentHeight 就是说明 刚跟新的就是最高的?
        其实不一定啊,现在我这 三列中第二列最短 ,我下一个cell是往第二列上更新 更新一个很矮的 那么还是这一列最短啊.
        所以 少不了的循环 遍历找到最高的列吧.
      • 7b3f15cccda8:不错哦
      • 芒果kuki:为什么我设置attrs.fram的时候使用arc4
        random(300)报错呢,
      • 6de54c2e26a0:写的很详细,感谢分享
      • 8dfd2aef0c03:很详细,作者很用心,感谢分享
      • 6f4f28b41ee8:哥们,发现一个错误点。- (CGFloat)columnCountInWaterflowLayout:(CYWaterFlowLayout *)waterflowLayout;返回的类型应该是NSInteger.应该是手误吧,点语法举例的时候写对了。有一个问题不明白,刷新数据移除之前的高后,怎么比较哪个cell是最低的。感觉按照第一行的思路比较解释不清,因为刷新前cell的高不相同。
        Tuberose:@J残骸枫 你说的错误点在这里没什么关系的,不必深究。至于怎么比较哪个cell是最低的。代码里面已经给你详细的写了,仍然是要进行判断。自己看吧,因为在里面已经很详细
      • Lin__Chuan:不错
      • ZHU大仙:好mark

      本文标题:瀑布流小框架

      本文链接:https://www.haomeiwen.com/subject/zwnhhttx.html