目标效果

因为系统给我们提供的 UICollectionViewFlowLayout 布局类不能实现瀑布流的效果,如果我们想实现 瀑布流 的效果,需要自定义一个 UICollectionViewLayout  类,实现瀑布流效果。效果如右图。

依赖工具:

我们需要一个图片大小和图片地址的Josn数据, 和 SDWebImage图片加载的第三方工具

RootViewController.m

 #import "RootViewController.h"
#import "DataModel.h"
#import "WaterFlowLayout.h"
#import "RootCell.h"
#import "UIImageView+WebCache.h" @interface RootViewController ()<UICollectionViewDataSource, UICollectionViewDelegate, WaterFlowLayoutDelegate> // 声明大数组存放所有的数据
@property (nonatomic, strong) NSMutableArray *allDataArray; // 定义collectionView
@property (nonatomic, strong) UICollectionView *collectionView; @end @implementation RootViewController // 懒加载
- (NSMutableArray *)allDataArray {
if (!_allDataArray) {
_allDataArray = [NSMutableArray array];
}
return _allDataArray;
} - (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view. // 读取数据
[self loadData]; // 初始化布局
[self initLayout]; // 注册cell
[self.collectionView registerClass:[RootCell class] forCellWithReuseIdentifier:@"cell"];
} // 初始化布局
- (void)initLayout { // 1.创建UICollectionView的布局样式对象
WaterFlowLayout *water = [[WaterFlowLayout alloc] init];
CGFloat width = ([UIScreen mainScreen].bounds.size.width - ) / ;
water.itemSize = CGSizeMake(width, width);
// 设置内边距
water.sectionInsets = UIEdgeInsetsMake(, , , );
// 设置间距
water.spacing = ;
// 设置有多少列
water.numberOfColumn = ;
// 设置代理
water.delegate = self; // 2.布局UICollectionView
self.collectionView = [[UICollectionView alloc] initWithFrame:self.view.frame collectionViewLayout:water];
self.collectionView.delegate = self;
self.collectionView.dataSource = self;
self.collectionView.backgroundColor = [UIColor whiteColor];
[self.view addSubview:self.collectionView]; } // 读取数据
- (void)loadData { // 第一步:获取文件路径
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"Data" ofType:@"json"];
// 第二步:根据路径读取数据,转为NSData对象
NSData *data = [NSData dataWithContentsOfFile:filePath];
// 第三步:根据json格式解析数据
NSArray *dataArray = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil]; // NSLog(@"%@", dataArray);
// 第四步:遍历数组,将数据转为model对象
for (NSDictionary *dict in dataArray) { // 创建model对象
DataModel *model = [[DataModel alloc] init];
// 使用KVC给model赋值
[model setValuesForKeysWithDictionary:dict]; // 将model添加到大数组中
[self.allDataArray addObject:model];
}
// NSLog(@"%@", self.allDataArray);
} // 设置分区个数
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
return ;
}
// 设置每个分区的item个数
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return self.allDataArray.count;
} // 返回每一个item的cell对象
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { RootCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"cell" forIndexPath:indexPath]; // 设置cell数据 DataModel *model = self.allDataArray[indexPath.row];
NSURL *url = [NSURL URLWithString:model.thumbURL];
[cell.photoView sd_setImageWithURL:url];
cell.backgroundColor = [UIColor orangeColor];
return cell;
} // 实现代理方法返回每一个item的高度
- (CGFloat)heightForItemAtIndexPath:(NSIndexPath *)indexPath { DataModel *model = self.allDataArray[indexPath.row];
CGFloat width = ([UIScreen mainScreen].bounds.size.width - ) / ;
// 计算item高度
CGFloat height = model.height / model.width * width;
return height; } @end

主视图文件,主要用于处理数据和布局页面

RootCell.h

 #import <UIKit/UIKit.h>

 @interface RootCell : UICollectionViewCell

 @property (nonatomic, strong) UIImageView *photoView;

 @end

RootCell.m

 #import "RootCell.h"

 @implementation RootCell

 - (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.photoView = [[UIImageView alloc] init];
[self.contentView addSubview:self.photoView];
}
return self;
} - (void)layoutSubviews { self.photoView.frame = self.bounds; } @end

RootCell就是每一个Item的样式, 也就是一张张图片

WaterFlowLayout.h

 #import <UIKit/UIKit.h>

 @protocol WaterFlowLayoutDelegate <NSObject>

 // 返回每一个item的高度
- (CGFloat)heightForItemAtIndexPath:(NSIndexPath *)indexPath; @end @interface WaterFlowLayout : UICollectionViewLayout // item的大小,需要根据这个获取宽度
@property (nonatomic, assign) CGSize itemSize; // 内边距的设置
@property (nonatomic, assign) UIEdgeInsets sectionInsets; // item的间距(这里水平方向和垂直方向的间距一样)
@property (nonatomic, assign) CGFloat spacing; // 列数
@property (nonatomic, assign) NSInteger numberOfColumn; // 设置代理,用于获取item的高度
@property (nonatomic, weak) id<WaterFlowLayoutDelegate>delegate; @end

WaterFlowLayout.m

 #import "WaterFlowLayout.h"

 @interface WaterFlowLayout ()

 // 声明私有属性
// 保存一共有多少个item
@property (nonatomic, assign) NSInteger numberOfItems; // 保存计算好的每一个item的信息
@property (nonatomic, strong) NSMutableArray *itemAttributes; // 保存每一列的高度
@property (nonatomic, strong) NSMutableArray *columnHeights; // 声明私有方法
// 找到当前最长列
- (NSInteger)indexOfHeightestColumn; // 找到当前最短列
- (NSInteger)indexOfShortestColumn; @end @implementation WaterFlowLayout // 懒加载
- (NSMutableArray *)itemAttributes {
if (!_itemAttributes) {
_itemAttributes = [NSMutableArray array];
}
return _itemAttributes;
} - (NSMutableArray *)columnHeights {
if (!_columnHeights) {
_columnHeights = [NSMutableArray array];
}
return _columnHeights;
} // 找到当前最长列
- (NSInteger)indexOfHeightestColumn {
// 记录最长列的下标
NSInteger index = ;
// 记录最长列的高度
CGFloat length = ;
for (int i = ; i < self.columnHeights.count; i++) {
// 将数组中的对象转为基本数值
CGFloat currentLength = [self.columnHeights[i] floatValue];
if (currentLength > length) {
length = currentLength;
index = i;
}
}
return index;
} // 找到当前最短列
- (NSInteger)indexOfShortestColumn {
NSInteger index = ;
CGFloat length = MAXFLOAT;
for (int i = ; i < self.columnHeights.count; i++) { CGFloat currentLength = [self.columnHeights[i] floatValue];
if (currentLength < length) {
length = currentLength;
index = i;
}
}
return index;
} // 接下来重写三个方法 // 准备布局,在这里计算每个item的frame
- (void)prepareLayout { // 拿到一共有多少个item
self.numberOfItems = [self.collectionView numberOfItemsInSection:];
// 每一列添加一个top高度
for (int i = ; i < self.numberOfColumn; i++) {
// @() NSNumber字面量创建对象
self.columnHeights[i] = @(self.sectionInsets.top);
} // 依次为每个item设置位置信息,并存储在数组中
for (int i = ; i < self.numberOfItems; i++) { // 1.找到最短列的下标
NSInteger shortestIndex = [self indexOfShortestColumn];
// 2.计算X 目标X = 内边距左间距 + (宽 + item间距)*最短列下标
CGFloat detalX = self.sectionInsets.left + shortestIndex * (self.itemSize.width + self.spacing);
// 3.找到最短列的高度
CGFloat height = [self.columnHeights[shortestIndex] floatValue];
// 4.计算Y
CGFloat detalY = height + self.spacing;
// 5.创建indexPath
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:]; // 6.调用代理方法计算高度
CGFloat itemHeight = ;
if (_delegate && [_delegate respondsToSelector:@selector(heightForItemAtIndexPath:)]) {
itemHeight = [_delegate heightForItemAtIndexPath:indexPath];
} // 定义保存位置信息的对象
UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; // 7.生成frame
attributes.frame = CGRectMake(detalX, detalY, self.itemSize.width, itemHeight);
// 8.将位置信息添加到数组中
[self.itemAttributes addObject:attributes]; // 9.更新这一列的高度
self.columnHeights[shortestIndex] = @(detalY + itemHeight);
} } // 返回UICollectionView的大小
- (CGSize)collectionViewContentSize { // 求最高列的下标
NSInteger heightest = [self indexOfHeightestColumn];
// 最高列的高度
CGFloat height = [self.columnHeights[heightest] floatValue];
// 拿到collectionView的原始大小
CGSize size = self.collectionView.frame.size;
size.height = height + self.sectionInsets.bottom; return size;
} // 返回每一个item的位置信息
- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {
return self.itemAttributes;
} @end

WaterFlowLayout 就是我们自定义的 瀑布流 的文件

DataModel.h

 #import <Foundation/Foundation.h>

 @interface DataModel : NSObject

 @property (nonatomic, copy) NSString *thumbURL;

 @property (nonatomic, assign) float width;

 @property (nonatomic, assign) float height;

 @end

DataModel.m

 #import "DataModel.h"

 @implementation DataModel

 // 防崩
- (void)setValue:(id)value forUndefinedKey:(NSString *)key { } @end

自定义UICollectionViewLayout之瀑布流的更多相关文章

  1. iOS自定义UICollectionViewLayout之瀑布流

    目标效果 因为系统给我们提供的 UICollectionViewFlowLayout 布局类不能实现瀑布流的效果,如果我们想实现 瀑布流 的效果,需要自定义一个 UICollectionViewLay ...

  2. 自定义UICollectionViewLayout 实现瀑布流

    今天研究了一下自定义UICollectionViewLayout. 看了看官方文档,要自定义UICollectionViewLayout,需要创建一个UICollectionViewLayout的子类 ...

  3. iOS开发笔记15:地图坐标转换那些事、block引用循环/weak–strong dance、UICollectionviewLayout及瀑布流、图层混合

    1.地图坐标转换那些事 (1)投影坐标系与地理坐标系 地理坐标系使用三维球面来定义地球上的位置,单位即经纬度.但经纬度无法精确测量距离戒面积,也难以在平面地图戒计算机屏幕上显示数据.通过投影的方式可以 ...

  4. OC - 29.自定义布局实现瀑布流

    概述 瀑布流是电商应用展示商品通常采用的一种方式,如图示例 瀑布流的实现方式,通常有以下几种 通过UITableView实现(不常用) 通过UIScrollView实现(工作量较大) 通过UIColl ...

  5. iOS---UICollectionView自定义流布局实现瀑布流效果

    自定义布局,实现瀑布流效果 自定义流水布局,继承UICollectionViewLayout 实现一下方法 // 每次布局之前的准备 - (void)prepareLayout; // 返回所有的尺寸 ...

  6. iOS开发进阶 - 自定义UICollectionViewLayout实现瀑布流布局

    移动端访问不佳,请访问我的个人博客 最近项目中需要用到瀑布流的效果,但是用UICollectionViewFlowLayout又达不到效果,自己动手写了一个瀑布流的layout,下面是我的心路路程 先 ...

  7. windowsphone 瀑布流&ui虚拟化

    瀑布流已经有点年代了吧,不过wp上还真是挺少资料的.今天抽空把自己之前搞过的东西写出来,避免大家重复劳动. 一.简单的瀑布流排版加入ui虚拟化. 最近看了 段博琼  ui虚拟化的一篇博文,链接:htt ...

  8. Objectiv-c - UICollectionViewLayout自定义布局-瀑布流

    最近刚写的一个简单的瀑布流. 整体思路可能不是很完善. 不过也算是实现效果了. 高手勿喷 思路: 自定义UICollectionViewLayout实际上就是需要返回每个item的fram就可以了. ...

  9. 自定义UICollectionViewLayout 布局实现瀑布流

    自定义 UICollectionViewLayout 布局,实现瀑布流:UICollectionView和UICollectionViewCell 另行创建,这只是布局文件, 外界控制器只要遵守协议并 ...

随机推荐

  1. Mysql undo与redo Log

    http://mysql.taobao.org/monthly/2015/04/01/ http://www.cnblogs.com/Bozh/archive/2013/03/18/2966494.h ...

  2. [Android]在代码混淆中关闭 Log

    -assumenosideeffects class android.util.Log{ public static *** d(...); public static *** e(...); }

  3. cascade 介绍与用法 ( oracle)

    级联删除,比如你删除某个表的时候后面加这个关键字,会在删除这个表的同时删除和该表有关系的其他对象 1.级联删除表中的信息,当表A中的字段引用了表B中的字段时,一旦删除B中该字段的信息,表A的信息也自动 ...

  4. Python中如何读取xml的数据

    <?xml version="1.0" encoding="utf-8" ?> - <catalog> <maxid>4&l ...

  5. mysql可以用这种方式<<! 输入内容 ! 做成脚本

    以这种文件式做交接NB!!!!! [root@NB test]# mysql -uroot -p$passwd <<! > use mysql > select user,ho ...

  6. SQLServer内置函数

    类型转换函数cast和convert --cast一般更容易使用,convert的优点是可以格式化日期和数值 select CAST('123.4' as int) --失败 select CONVE ...

  7. JavaWeb学习之转发和重定向、会话技术:cookie、session、验证码实例、URLConnection使用(下载网页)(4)

    1.转发和重定向 HttpServletResponse response 转发: RequestDispatcher dispatcher = request.getRequestDispatche ...

  8. 【PHP&&MySQL详解】

    PHP和MySQL是一对好搭档,PHP中有一个很大的扩展库专门针对对MySQL的操作.当然,作为一个PHP程序员,首先对MySQL的增删查改要非常熟悉才行. MySQL数据库的连接数大概在6w个左右, ...

  9. 【PHP构造方法和析构方法的使用】

    构造方法:__construct,析构方法:__destruct 代码示例: <?php class Person { public $name; public $age; public fun ...

  10. 算法系列:geometry

    1.基本几何变换及变换矩阵 基本几何变换都是相对于坐标原点和坐标轴进行的几何变换,有平移.比例.旋转.反射和错切等. 1.1 平移变换 是指将p点沿直线路径从一个坐标位置移到另一个坐标位置的重定位过程 ...