控制器中:
@interface ViewController () <UICollectionViewDelegateFlowLayout,UICollectionViewDataSource>
@property (nonatomic,strong) UICollectionView *collectionView;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
WaterFlowLayOut *layout = [[WaterFlowLayOut alloc] init];
_collectionView = [[UICollectionView alloc] initWithFrame:[UIScreen mainScreen].bounds collectionViewLayout:layout];
_collectionView.delegate = self;
_collectionView.dataSource = self;
_collectionView.backgroundColor = [UIColor lightGrayColor];
[self.view addSubview:_collectionView];
NSMutableArray *tmpArray = [NSMutableArray array];
for (int i = 21; i >= 1; i--) {
NSString *imageName = [NSString stringWithFormat:@"%d.jpg",i];
NSString *path = [[NSBundle mainBundle] resourcePath];
path = [path stringByAppendingString:[NSString stringWithFormat:@"/%@",imageName]];
NSLog(@"%@",imageName);
UIImage *image = [[UIImage alloc] initWithContentsOfFile:path];
[tmpArray addObject:image];
}
[tmpArray addObjectsFromArray:tmpArray];
[tmpArray addObjectsFromArray:tmpArray];
self.images = tmpArray;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return self.images.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
static NSString *identifier = @"cell";
// 1. 注册
[_collectionView registerClass:[WaterFallCell class] forCellWithReuseIdentifier:identifier];
// 2. 复用池中寻找或者是直接创建
WaterFallCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
// 给cell图片,之后,然后cell自己描绘
cell.image = self.images[indexPath.item];
return cell;
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
UIImage *image = self.images[indexPath.item];
float height = [self imgHeight:image.size.height width:image.size.width];
return CGSizeMake(100, height);
}
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {
return UIEdgeInsetsMake(5, 5, 5, 5);
}
- (float)imgHeight:(float)height width:(float)width {
// 根据宽100 通过比例或者当前图片的高度 每个图片高度不一样
float newHeight = height/width*100;
// newHeight/100 = height/width;
return newHeight;
}
cell中
#import "WaterFallCell.h"
@implementation WaterFallCell
- (void)setImage:(UIImage *)image {
if (_image != image) {
_image = image;
}
// 通过此方法调用drawRect方法
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect {
// 根据比例算出应该的高度
float newHeight = _image.size.height/_image.size.width*100;
[_image drawInRect:CGRectMake(0, 0, 100, newHeight)];
}
自定义layout继承自UICollectionViewFlowLayout
#define Cols_Count 3
#import "WaterFlowLayOut.h"
@interface WaterFlowLayOut ()
@property (nonatomic,strong) NSMutableDictionary *attributesDic;
/**
* cell个数
*/
@property (nonatomic,assign) NSInteger cellCount;
/** 列高数组*/
@property (nonatomic,strong) NSMutableArray *colHeightArr;
/**
* 间隙
*/
@property (nonatomic,assign) UIEdgeInsets edge;
@end
@implementation WaterFlowLayOut
/**
* 1. 在这个方法中为每个cell做布局,为每个cell确定自己的位置,此处用三列展示图片,列数可以随意设置,取决于图片的宽度
首先创建一个数组,存储三列的高度,找出最短列。
将cell插入到最短列,而cell的高度和宽度 可以通过调用前面视图控制器实现的代理协议得到
*/
/**
* 此方法中用了很多 self.delegate来调用代理方法 self.delegate即帮助来显示布局的控制器 collectionView的代理和布局的代理是同一个-》控制器
*/
- (void)prepareLayout {
[super prepareLayout];
_attributesDic = [NSMutableDictionary dictionary];
// 让collectionView的代理 做 layout的代理
self.delegate = (id<UICollectionViewDelegateFlowLayout>) self.collectionView.delegate;
// 求出总共有多少个cell
self.cellCount = [self.collectionView numberOfItemsInSection:0];
// 如果0个cell直接就不layout了
if (self.cellCount == 0) {
return;
}
self.colHeightArr = [NSMutableArray array]; /// 列数,此处为三列,数组中存储每列的高度
float top = 0;
// 初始话高度都是0
for (int i = 0; i < Cols_Count; i++) {
[self.colHeightArr addObject:[NSNumber numberWithFloat:top]];
}
// 循环调用layoutAttributesForItemAtIndexPath方法为 每个cell计算布局 ,将indexPath传入 作为布局字典的key
for (int i = 0; i < self.cellCount; i++) {
// 这样写是完全错误的,collectionView的cell的位置是用indexPathForItem来定义的 ,这个ForRow是用来定义表视图的
// / [self layoutForItemAtIndexPath:[NSIndexPath indexPathForRow:i inSection:0]];
[self layoutForItemAtIndexPath: [NSIndexPath indexPathForItem:i inSection:0]];
}
}
/**
* 这里用到了一个布局字典,
其实就是将每个cell的位置信息与indexPath相对应,将它们存入字典中,之所以用到这个字典,是为了后面视图的检索
思路,将每个cell的frame动态算出,然后将其与自己特定的indexPath对应,放入字典,后面视图的检索(只返回屏幕可显示的cell不会一次全部返回所有cell)
*/
// 这个方法会调用多次,为每个cell布局
- (void)layoutForItemAtIndexPath:(NSIndexPath *)indexPath {
// 1. 间隙
// 控制器调用方法(协议方法)来获取cell间的间隙
UIEdgeInsets edgeInsets = [self.delegate collectionView:self.collectionView layout:self insetForSectionAtIndex:indexPath.row];
// 间隙
self.edge = edgeInsets;
// 2. 大小
// 每个cell的size
CGSize itemSize = [self.delegate collectionView:self.collectionView layout:self sizeForItemAtIndexPath:indexPath];
NSInteger col = 0; // 列数
// 找出高度最小列,然后将cell加入到高度最小列中
float shortHeight = [self.colHeightArr[col] floatValue];
// NSLog(@"count = %ld",self.colHeightArr.count);
// 遍历存放每列高度的数组
for (int i = 1; i < self.colHeightArr.count; i++) {
float colHeight = [self.colHeightArr[i] floatValue];
if (colHeight < shortHeight) {
shortHeight = colHeight;
// 高度最小列的索引保存
col = i;
}
}
// 3.存放高度最小列高度
float top = [self.colHeightArr[col] floatValue];
// NSLog(@"top = %.1f",top);
// 确定每个cell的frame
// 起始位置x为 间距x + 前面的所有cell的宽和间隙 之和
CGFloat cellX = edgeInsets.left + col * (edgeInsets.left+itemSize.width);
// 起始位置y为 高度最小列的高度 + 间隙
CGFloat cellY = top + edgeInsets.top;
NSLog(@"celly:%.1f",cellY);
// 宽度
CGFloat cellWidth = itemSize.width;
// 高度
CGFloat cellHeight = itemSize.height;
NSLog(@"celleheight = %.1f",cellHeight);
// NSLog(@"%f",cellHeight);
CGRect cellFrame = CGRectMake(cellX, cellY, cellWidth, cellHeight);
//NSLog(@"%@",NSStringFromCGRect(cellFrame));
// NSLog(@"%f",cellFrame.size.height);
// 4.cell加入后,更新列高
[self.colHeightArr replaceObjectAtIndex:col withObject:[NSNumber numberWithFloat:top + edgeInsets.top + itemSize.height]];
// 5.每一个cell的frame对应一个indexPath,存入字典
// [NSIndexPath indexPathForItem:i inSection:0]; 每一个cell都有一个indexPath
[self.attributesDic setObject:indexPath forKey:NSStringFromCGRect(cellFrame)];
}
/**
* 此方法会传入一个collectionView当前可见的rect,视图滑动时候调用
// 需要返回每个cell'的布局信息,通过indexPath返回 (在rect范围内的)
*
需要返回每个cell的布局信息,如果忽略传入的rect一次性将所有的cell布局信息返回,,通过下面的两个方法,每次只将屏幕需要显示的cell返回,就不会造成图片过多时性能很差的效果
// UICollectionViewLayoutAttributes这个类包含了collectView内item的所有相关布局属性
*/
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
// Returns the layout attributes for all of the cells and views in the specified rectangle.
// api解释;这个方法用来返回特定范围内的‘所有’ cell和视图的 layout属性
/**
* 1. 思路,怎么将特定范围内的所有的cell和视图的布局信息返回
解决,每个cell都有特定的indexPath,通过indexPath得到每个cell的布局信息,然后加入数组返回即可!!
// 但是所有的cell的indexPath们怎么取到? 必须得自己写方法来获取indexPathOfItemsInRect
通过indexPathOfItemsInRect得到了所有的cell的indexPath们。这个时候就可以获取需要展示的所有的cell的布局信息
同时这个过程中,需要layoutAttributesForItemAtIndexPath返回每个cell的布局信息。
*/
NSMutableArray *attributes = [NSMutableArray array];
// 调用indexPathOfItemInRect方法,将rect传入,计算当前rect应该出现的cell,返回值为cell的indexPath数组
NSArray *indexPaths = [self indexPathOfItemsInRect:rect];
for (NSIndexPath *indexPath in indexPaths) {
//
UICollectionViewLayoutAttributes *attribute = [self layoutAttributesForItemAtIndexPath:indexPath];
/**
* frame center bounds size alpha等
*/
[attributes addObject:attribute];
}
return attributes;
}
// 为每个cell布局完毕之后,还要实现一个很重要的方法
- (NSArray *)indexPathOfItemsInRect:(CGRect)rect {
/**
* 遍历布局字典通过CGRectInterssectsRect方法确定每一个cell的rect和传入的rect是否有交集,如果为true,则cell应该显示,
将布局字典中对应的indexPath存入数组中
*/
NSMutableArray *indexPaths = [NSMutableArray array];
for (NSString *rectString in self.attributesDic.allKeys) { // 此时self.attributesDic中以及保存了key是frame,值是indexPath的字典,而且是全部cell的,我们只需要遍历这个字典,如果这个字典中的cell的rect在当前显示的范围,此处为屏幕内,就取出indexPath加入数组,准备展示!!
// {@"rectString":@indexPath}
CGRect cellFrame = CGRectFromString(rectString);
// 如果有交集 则将indexPath取出存入数组中
if (CGRectIntersectsRect(rect, cellFrame)) {
NSIndexPath *indexPath = self.attributesDic[rectString];
[indexPaths addObject:indexPath];
}
}
return indexPaths;
}
/**
* 每一个cell的属性布局信息,
* 返回单个cell属性布局信息
*/
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
// [self.attributesDic setObject:indexPath forKey:NSStringFromCGRect(cellFrame)];
UICollectionViewLayoutAttributes *attribute = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
for (NSString *rectString in self.attributesDic.allKeys) {
if (self.attributesDic[rectString] == indexPath) {
attribute.frame = CGRectFromString(rectString);
return attribute;
}
}
return nil;
}
// collectionView的内容高度
- (CGSize)collectionViewContentSize {
CGSize size = self.collectionView.frame.size;
// 找出三列中最高的一列,作为collectionView的高度
float maxHeight = [self.colHeightArr[0] floatValue];
for (int i = 1; i < self.colHeightArr.count; i++) {
float colHeight = [self.colHeightArr[i] floatValue];
if (colHeight > maxHeight) {
maxHeight = colHeight;
}
}
size.height = maxHeight;
return size;
}