IOS 开发笔记——如何用 collection 创建瀑布流

知道 collectionView 的读者都知道,我们在创建 collectionView 的时候,我们就要为 collectionView 添加一个布局
[[UICollectionView alloc] initWithFrame:<#(CGRect)#> collectionViewLayout:<#(nonnull UICollectionViewLayout *)#>]
在这个方法中,我们不得不传入一个 UICollectionViewLayout 的对象进入,而这个对象,就是我们所说的布局,通过这个布局,我们可以实现无限可能的布局,只有你想象不到的,今天,我们来做一做流水布局以下是效果图
IOS 开发笔记——如何用 collection 创建瀑布流_第1张图片

一开始,我们要为 collectionView 创建一个布局子类,我们就命名为 ABSWaterflowLayout 吧

@interface ABSWaterFlowLayout : UICollectionViewLayout
@end

之后我们要创建 collectionView 显示的 cell,这个 cell 当然也是继承 uicollectionViewCell 了,因为我们要在里面添加一个图片,这个就要自定义了

#import 
@interface ABSCollectionViewCell : UICollectionViewCell
@end

然后我们在 ViewController 中创建我们的主角 collectionView

#import 
@interface ViewController : UIViewController
@end

#import "ViewController.h"
@interface ViewController ()<UICollectionViewDataSource, UICollectionViewDelegate>
@property (nonatomic,strong) UICollectionView  *collectionView;
/** 数据源*/
@property (nonatomic,strong) NSArray  *dataSource; 
@end

- (void)viewDidLoad {
    [super viewDidLoad];
    // 创建我们的流水布局
    ABSWaterFlowLayout *waterFlowLayout = [[ABSWaterFlowLayout alloc]init];
self.collectionView = [[UICollectionView alloc]initWithFrame:self.view.bounds collectionViewLayout:waterFlowLayout];
    self.collectionView.backgroundColor = [UIColor yellowColor];
    // 设置代理
    self.collectionView.delegate = self;
    // 设置数据源
    self.collectionView.dataSource = self;
    // 注册 cell
    [self.collectionView registerClass:[ABSCollectionViewCell class] forCellWithReuseIdentifier:identifier];
    [self.view addSubview:self.collectionView];

    // 安全释放
    TT_RELEASE_SAFELY(waterFlowLayout);

}
//  UICollectionViewDataSource
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
    return 1;
}

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
    return self.dataSource.count;
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    ABSCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
    cell.backgroundColor = [UIColor redColor];
    if ([self.dataSource[indexPath.row] isKindOfClass:[Shop class]]) {
        Shop *shop = self.dataSource[indexPath.row];
        cell.shop = shop;
    }
    return cell;
    TT_RELEASE_SAFELY(cell);
}

好了,接下来,我们就安全排布好我们的 ABSWaterflower 布局了,观测我们的效果图,可以看出,每一列和每一行之间都有一个间距的,而这些间距又是让用户可以修改的,为了这个布局以后都能自定义化,我们的设计要更加符和面向对象的特性

#import 
@class ABSWaterFlowLayout;
@protocol ABSWaterFlowLayoutDelegate 
// 询问它的代理,indexPath 这个位置的 cell 的高度为多少
- (CGFloat)waterflowLayout:(ABSWaterFlowLayout *)waterflowLayout heightForWidth:(CGFloat)width atIndexPath:(NSIndexPath *)indexPath;

@end

@interface ABSWaterFlowLayout : UICollectionViewLayout

/** 每一列的间距*/
@property (nonatomic, assign) NSInteger columnMargin;
/** 每一行的间距*/
@property (nonatomic, assign) NSInteger rowMargin;
/** 每一组的间距*/
@property (nonatomic, assign) UIEdgeInsets sectionInset;
/** 有多少列*/
@property (nonatomic, assign) NSInteger coloumnsCount;

@property (nonatomic, weak) id delegate;

@end
#import "ABSWaterFlowLayout.h"

@interface ABSWaterFlowLayout()
/** 这个字典用来存储每一列最大的 Y 值(每一列的高度)*/
// 如果coloumnsCount只有3列,那么这个 maxDict 就像如此 @{@"0":@(0), @"1":@(0), @"2":@(2)}
@property (nonatomic,strong) NSMutableDictionary *maxDict;
// 这个数组装载着每一个 UICollectionViewcell 的布局属性
@property (nonatomic,strong) NSMutableArray  *attrsArray;
@end

@implementation ABSWaterFlowLayout

- (instancetype)init {
    if (self = [super init]) {
    // 如果用户没有设置这些值,我们必须为用户设置一个默认值
        self.columnMargin = 10;
        self.rowMargin = 10;
        self.sectionInset = UIEdgeInsetsMake(10, 10, 10, 10);
        self.coloumnsCount = 3;
    }
    return self;
}

- (void)dealloc
{
// 安全释放
    TT_RELEASE_SAFELY(self.maxDict);
    TT_RELEASE_SAFELY(self.attrsArray);
}
#pragma mark - lazy
- (NSMutableDictionary *)maxDict
{
    if (!_maxDict ) {
        _maxDict = [[NSMutableDictionary alloc]init];
        for (int i = 0; i < self.coloumnsCount; i ++) {
            NSString *column = [NSString stringWithFormat:@"%d",i];
            self.maxDict[column] = @0;
        }
    }
    // @"0" : @(0), @"1" : @(0) , @"2" : @(0) 这种格式来存储这一列最大的 Y 值
    return _maxDict;
}

- (NSMutableArray *)attrsArray
{
    if (!_attrsArray ) {
        _attrsArray = [[NSMutableArray alloc]init];
    }
    return _attrsArray;
}
// 准备布局的时候先调用这个方法
- (void)prepareLayout {
    [super prepareLayout];

    for (int i = 0; i < self.coloumnsCount; i ++) {
        NSString *column = [NSString stringWithFormat:@"%d",i];
        self.maxDict[column] = @(0);
    }

    // 每次布局之前,先把之前的cell 的布局属性都给删掉,然后重新设置
    [self.attrsArray removeAllObjects];
    // 第0组的 item 的数量
    NSInteger count = [self.collectionView numberOfItemsInSection:0];
    for (int i = 0; i < count; i ++) {
        UICollectionViewLayoutAttributes *attrs = [self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:i inSection:0]];
        [self.attrsArray addObject:attrs];
        TT_RELEASE_SAFELY(attrs);
    }
}
// 返回内容尺寸
- (CGSize)collectionViewContentSize {
    __block NSString *maxColumn = @"0";
    // 找出 maxDict 字典中最后最大的行的的高度
    [self.maxDict enumerateKeysAndObjectsUsingBlock:^(NSString *column, NSNumber *obj, BOOL * _Nonnull stop) {
        if (self.maxDict[column] > self.maxDict[maxColumn] ) {
            maxColumn = column;
        }
    }];
    return CGSizeMake(0, [self.maxDict[maxColumn] floatValue]);
}
// 在指定范围内返回每一个 cell 的布局属性
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
 // 返回所有布局属性
    return self.attrsArray;
}
// 为indexPath 位置中的 cell 返回一个布局属性,告诉这个 cell 改放在哪一个位置(frame)
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
    // 假设最短的那一列是第0列
    __block NSString *minColum = @"0";
    // 找出最短的那一列
    [self.maxDict enumerateKeysAndObjectsUsingBlock:^(NSString *column, NSNumber *maxY, BOOL * _Nonnull stop) {
        if ([maxY floatValue] < [self.maxDict[minColum] floatValue]) {
            minColum = column;
        }
    }];

    // 计算宽度
    CGFloat width = (self.collectionView.frame.size.width - self.sectionInset.left - self.sectionInset.right - (self.coloumnsCount - 1) * self.columnMargin ) / self.coloumnsCount;
    CGFloat height = [self.delegate waterflowLayout:self heightForWidth:width atIndexPath:indexPath];

    // 计算位置
    CGFloat x = self.sectionInset.left + (width + self.columnMargin) * [minColum intValue];
    CGFloat y = self.rowMargin + [self.maxDict[minColum] floatValue];

    // 更新这一列最大的 Y 值
    self.maxDict[minColum] = @(y + height);

    // 创建属性
    UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
    attrs.frame = CGRectMake(x, y, width, height);

    return attrs;
}

好了,至此我们的瀑布流已经完成了,剩下的就只有把 DataSour 的数据远接入,再着就是为ABSWaterFlowLayoutDelegate 返回高度即可以下是我们源代码,欢迎大家查看,(^__^)
http://www.cocoachina.com/bbs/read.php?tid=326153

你可能感兴趣的:(IOS开发笔记)