iOS瀑布流

说来惭愧,使用collectionView这么久了,还从来没自己写过瀑布流,废话不多说,先上效果图:

效果图

这是GitHub地址

数据来源

数据源来自项目中的shop.plist
使用MJExtension转为模型HXShopItem
保存在shops数组里
列表刷新用的MJRefresh
图片加载使用SDWebImage

  • 加载数据时使用了GCD延时一秒,假装有网络延时
- (void)loadNewShops {
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                NSArray *shops = [HXShopItem objectArrayWithFilename:@"shop.plist"];
                [self.shops removeAllObjects];
                [self.shops addObjectsFromArray:shops];
                [self.collectionView reloadData];
                [self.collectionView.header endRefreshing];
        });
}
数据源方法
#pragma UICollectionViewDataSource
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
        self.collectionView.footer.hidden = self.shops.count == 0;
        return self.shops.count;
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
        HXCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"HXCollectionViewCell" forIndexPath:indexPath];
        cell.shop = self.shops[indexPath.item];
        return cell;
}

瀑布流布局的HXCollectionViewLayout:

cell的布局属性完全取决于Layout,不得不佩服苹果把它封装的这么完美

  • Layout的代理 :
@protocol HXCollectionViewLayoutDelegate <NSObject>

@required
//获取每个cell的高度
- (CGFloat)waterFlowLayout:(HXCollectionViewLayout *)waterFlowLayout heigthForItemAtIndex:(NSUInteger)index itemWidth:(CGFloat)itemWidth;

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

代理中是一些获取布局属性的方法
我喜欢写成代理的方式去获取而不是设置成属性给外界赋值
因为设置成属性的话
就要等待外界的赋值,而且外界可以随时修改
这样的话就要监听属性的set方法来刷新数据,感觉略为被动
如果写成代理则是调用代理方法主动去获取属性,有主动权

  • Layout中用到的属性
@interface HXCollectionViewLayout()
//存放每一列高度的数组
@property(nonatomic,strong) NSMutableArray *columnHeights;
//存放所有cell布局属性的数组
@property(nonatomic,strong) NSMutableArray *attributesArray;
//行间距
@property(nonatomic,assign) CGFloat rowMargin;
//列间距
@property(nonatomic,assign) CGFloat columnMargin;
//列数
@property(nonatomic,assign) NSInteger columnCount;
//边距
@property(nonatomic,assign) UIEdgeInsets edgeInsets;
@end

/** 默认的列数 */
static const NSInteger HXDefaultColumnCount = 3;
/** 每一列之间的间距 */
static const CGFloat HXDefaultColumnMargin = 10;
/** 每一行之间的间距 */
static const CGFloat HXDefaultRowMargin = 10;
/** 边缘间距 */
static const UIEdgeInsets HXDefaultEdgeInsets = {10, 10, 10, 10};

  • Layout的初始化方法
    在初始化方法中,做了这几件事:
    清除列高数组中所有的数据
    然后添加初始化高度 (就是上边距)
    清除所有cell的布局属性
    调用layoutAttributesForItemAtIndexPath
    重新计算每个cell的布局属性
    (因为初始化方法调用一次,又因为cell位置大小是确定的,所以在初始化方法里计算一次就可)
- (void)prepareLayout {
        [super prepareLayout];
        //清除以前计算的所有高度
        [self.columnHeights removeAllObjects];
        for (NSInteger i = 0; i < self.columnCount; i++) {
                [self.columnHeights addObject:@(self.edgeInsets.top)];
        }
        //清除之前所有的布局属性
        [self.attributesArray removeAllObjects];
        //开始创建新的布局属性
        NSInteger count = [self.collectionView numberOfItemsInSection:0];
        for (NSInteger i = 0; i < count; i++) {
                NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
                //获取indexPath位置对应的cell布局属性
                UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForItemAtIndexPath:indexPath];
                [self.attributesArray addObject:attributes];
        }
}
  • layoutAttributesForItemAtIndexPath方法
    这个方法做了这几件事:
    计算collectionView的宽度
    根据collectionView宽度和列数计算cell宽度
    调用代理方法获取cell的高度
    查找所有列中最短的一列,根据这一列的高度算出cell的y坐标
    根据列数计算x坐标
    这样cell的frame就计算出来了
    更新最短那列的高度
//返回每个indexPath对应的cell的布局属性
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
        UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
        //collectionView的宽度
        CGFloat collectionViewWidth = self.collectionView.frame.size.width;
        //布局的宽度和高度
        CGFloat width = (collectionViewWidth - self.edgeInsets.left - self.edgeInsets.right - (self.columnCount - 1)*self.columnMargin) / self.columnCount;
        CGFloat height = [self.delegate waterFlowLayout:self heigthForItemAtIndex:indexPath.item itemWidth:width];
        
        //查找对短的一列
        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 (columnHeight < minColumnHeight) {
                        minColumnHeight = columnHeight;
                        destColumn = i;
                }
        }
        CGFloat x = self.edgeInsets.left + destColumn*(width + self.columnMargin);
        CGFloat y = minColumnHeight;
        if (y != self.edgeInsets.top) {
                y += self.rowMargin;
        }
        attributes.frame = CGRectMake(x, y, width, height);
        
        //更新最短那列的高度
        self.columnHeights[destColumn] = @(CGRectGetMaxY(attributes.frame));
        return attributes;
}
  • 返回布局数组方法
    这个方法调用频繁
    所以计算cell的布局属性不在这里
    这里把初始化方法中计算好的属性返回即可
//返回布局数组
- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {
        return self.attributesArray;
}
  • collectionViewContentSize方法
    根据最高的那一列
    算出collectionView的ContentSize即可
- (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, maxColumnHeight + self.edgeInsets.bottom);
}

这样就算完成了
使用HXCollectionViewLayout的时候,把他导入项目,实现其代理方法即可.


感谢阅读
你的支持是我写作的唯一动力

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容

  • 一、瀑布流设计方案 二、瀑布流设计思路分析 1、自定义流水布局中,指定滚动方向、默认列数、行间距、列间距、以及指定...
    iOS_成才录阅读 21,005评论 25 84
  • 传统!!!依然是效果演示 特点:可以自由设置瀑布流的总列数(效果演示为2列) 虽然iphone手机的系统相册没有使...
    Macgx阅读 1,696评论 14 13
  • 功能描述:WSLWaterFlowLayout 是在继承于UICollectionViewLayout的基础上封装...
    且行且珍惜_iOS阅读 19,367评论 43 90
  • 序言 前段时间开发的时候,需要在tableView上拉的时候实现最底下的cell随着滑动从左边移动出来的效果(淘宝...
    sindri的小巢阅读 10,467评论 18 38
  • 下面这些是外部封装的接口: @interface LRBWaterFallLayout : UICollectio...
    小啦啦啦熊阅读 547评论 0 0