下面这些是外部封装的接口:
@interface LRBWaterFallLayout : UICollectionViewLayout
@property (nonatomic) NSMutableArray * modelAry;
/**
* 最小行间距
*/
@property (nonatomic) CGFloat minimumLineSpacing;
/**
* 最小item之间的距离
*/
@property (nonatomic) CGFloat minimumInteritemSpacing;
/**
* 内边距
*/
@property (nonatomic) UIEdgeInsets sectionInset;
/**
* 总列数
*/
@property (nonatomic) NSUInteger columns;
@end
下面这些是那些常规的方法
//屏幕bounds#define SCREEN_BOUNSE ([UIScreen mainScreen].bounds)
//屏幕宽度#define SCREEN_WIDTH (SCREEN_BOUNSE.size.width)
@interface LRBWaterFallLayout ()
/** * 记录每一列的高度 */
@property (nonatomic) NSMutableArray *heightArray;
/** * 记录每一个item的属性 */
@property (nonatomic) NSMutableArray *attrArray;
@end
@implementation LRBWaterFallLayout
//重写方法
//准备布局,布局之前准备工作全部做完
- (void)prepareLayout{
//1.创建高度数组
if (self.heightArray == nil) {
//没有数组创建数组
self.heightArray = [NSMutableArray array];
}else{
//有数组将原来数组里面的内容全部清空
[self.heightArray removeAllObjects];
}
if (self.attrArray == nil) {
self.attrArray = [NSMutableArray array];
}else{
[self.attrArray removeAllObjects];
}
//2.初始化高度数组
for (int i = 0; i < self.columns; i++) {
[self.heightArray addObject:@(self.sectionInset.top)];
}
//3.计算item的宽度
CGFloat totalWidth = SCREEN_WIDTH;
CGFloat validWidth = totalWidth - self.sectionInset.left - self.sectionInset.right - (self.columns - 1) * self.minimumInteritemSpacing;
CGFloat itemWidth = validWidth / self.columns;
//4.开始布局每个item
//取出item的总个数
NSUInteger totalCount = [self.collectionView numberOfItemsInSection:0];
for (int i = 0; i < totalCount; i++) {
//取出最短列的下标
NSUInteger minIndex = [self minIndexOfColumn];
//求item的x坐标
CGFloat itemX = self.sectionInset.left + minIndex * (itemWidth + self.minimumInteritemSpacing);
//求item的y坐标
CGFloat itemY = [self.heightArray[minIndex] floatValue];
//得到item的高度
LRBFirstOneItemOfPicsModel * oneItems = self.modelAry[i]; LRBFirstInfoModel * firstInfo = oneItems.data.info;
CGFloat rate = [firstInfo.img_h floatValue] / [firstInfo.img_w floatValue]; CGFloat itemHeight = itemWidth * rate + 20;
//创建item的属性对象
NSIndexPath *itemIndexPath = [NSIndexPath indexPathForItem:i inSection:0]; UICollectionViewLayoutAttributes *attr = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:itemIndexPath];
//将item的frame存放到item的属性对象中
attr.frame = CGRectMake(itemX, itemY, itemWidth, itemHeight);
//将item属性对象,存放到属性数组中
[self.attrArray addObject:attr];
//更新高度数组中的高度
CGFloat height = [self.heightArray[minIndex] floatValue] + itemHeight + self.minimumLineSpacing ;
[self.heightArray replaceObjectAtIndex:minIndex withObject:@(height)];
}
}
//重写方法
//所有的item的属性对象,只要frame和rect有交集,这些属性对象我都要,将其存放到数组中返回
- (NSArray*)layoutAttributesForElementsInRect:(CGRect)rect
{
//存放所有和rect有交集的item属性对象
NSMutableArray *mulArray = [NSMutableArray array];
for (UICollectionViewLayoutAttributes *attr in self.attrArray) {
//判断每一个attr的frame和rect是否有交集,如果有交集的话存放到数组中
if (CGRectIntersectsRect(attr.frame, rect)) {
[mulArray addObject:attr];
}
}
return mulArray;
}
//重写方法
//返回集合视图的内容尺寸
- (CGSize)collectionViewContentSize
{
//得到所有列中最高列的下标
NSUInteger maxIndex = [self maxIndexOfColumn];
//获取内容尺寸的最大值
CGFloat height = [self.heightArray[maxIndex] floatValue] + self.sectionInset.bottom;
//每一个布局对象里面都有一个指针指向当前集合视图,可以通过这个指针拿到当前集合视图
CGFloat width = SCREEN_WIDTH;
//返回集合视图的内容尺寸
return CGSizeMake(width, height);
}
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
{
return YES;
}
//每列中最短列的下标
- (NSUInteger)minIndexOfColumn
{
NSUInteger index = 0;
for (int i = 1; i < self.heightArray.count; i++) {
if ([self.heightArray[index] floatValue] > [self.heightArray[i] floatValue]) {
index = i;
}
}
return index;
}
//每列中最长列的下标
- (NSUInteger)maxIndexOfColumn
{
NSUInteger index = 0;
for (int i = 1; i < self.heightArray.count; i++) {
if ([self.heightArray[index] floatValue] < [self.heightArray[i] floatValue]) {
index = i;
}
}
return index;
}
@end
然而只有上面的这些的时候,在一开始就将item的个数确定,以及不需要复用的时候是可行的。在需要复用的时候,会出错,错误提示为:*** Assertion failure in -[UICollectionViewData layoutAttributesForItemAtIndexPath:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit_Sim/UIKit-3512.30.14/UICollectionViewData.m:666
为了解决上述问题,实现方法[UICollectionViewData layoutAttributesForItemAtIndexPath:],对每个item的属性进行设置,即可解决,实现方法如下:
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewLayoutAttributes * attr = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
CGFloat totalWidth = SCREEN_WIDTH;
CGFloat validWidth = totalWidth - self.sectionInset.left - self.sectionInset.right - (self.columns - 1) * self.minimumInteritemSpacing;
CGFloat itemWidth = validWidth / self.columns;
NSUInteger minIndex = [self minIndexOfColumn];
//求item的x坐标
CGFloat itemX = self.sectionInset.left + minIndex * (itemWidth + self.minimumInteritemSpacing);
//求item的y坐标
CGFloat itemY = [self.heightArray[minIndex] floatValue];
LRBFirstOneItemOfPicsModel * oneItems = self.modelAry[indexPath.row];
LRBFirstInfoModel * firstInfo = oneItems.data.info;
CGFloat rate = [firstInfo.img_h floatValue] / [firstInfo.img_w floatValue];
CGFloat itemHeight = itemWidth * rate + 20;
attr.frame = CGRectMake(itemX, itemY, itemWidth, itemHeight);
return attr;
}
在使用storyboard拉出来的collectionView的时候,进行布局,可采用下述方法进行调用以实现瀑布流:
weakSelf.collectionV.collectionViewLayout = [self waterFallLayout];
- (UICollectionViewLayout *)waterFallLayout{
LRBWaterFallLayout * layout = [[LRBWaterFallLayout alloc] init];
layout.minimumInteritemSpacing = 10;
layout.minimumLineSpacing = 10;
layout.columns = 2;
layout.sectionInset = UIEdgeInsetsMake(10, 10, 10, 10);
layout.modelAry = _dataSource;
return layout;
}