自定义UICollectionViewLayout之瀑布流

来源:互联网 发布:最优化可行方向 编辑:程序博客网 时间:2024/04/30 18:34
目标效果

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

依赖工具:

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

 

 

 

 

 

 

 

 

 

RootViewController.m

  1 #import "RootViewController.h"  2 #import "DataModel.h"  3 #import "WaterFlowLayout.h"  4 #import "RootCell.h"  5 #import "UIImageView+WebCache.h"  6   7 @interface RootViewController ()<UICollectionViewDataSource, UICollectionViewDelegate, WaterFlowLayoutDelegate>  8   9 // 声明大数组存放所有的数据 10 @property (nonatomic, strong) NSMutableArray *allDataArray; 11  12 // 定义collectionView 13 @property (nonatomic, strong) UICollectionView *collectionView; 14  15 @end 16  17 @implementation RootViewController 18  19 // 懒加载 20 - (NSMutableArray *)allDataArray { 21     if (!_allDataArray) { 22         _allDataArray = [NSMutableArray array]; 23     } 24     return _allDataArray; 25 } 26  27 - (void)viewDidLoad { 28     [super viewDidLoad]; 29     // Do any additional setup after loading the view. 30      31     // 读取数据 32     [self loadData]; 33      34     // 初始化布局 35     [self initLayout]; 36      37     // 注册cell 38     [self.collectionView registerClass:[RootCell class] forCellWithReuseIdentifier:@"cell"]; 39 } 40  41 // 初始化布局 42 - (void)initLayout { 43      44     // 1.创建UICollectionView的布局样式对象 45     WaterFlowLayout *water = [[WaterFlowLayout alloc] init]; 46     CGFloat width = ([UIScreen mainScreen].bounds.size.width - 40) / 3; 47     water.itemSize = CGSizeMake(width, width); 48     // 设置内边距 49     water.sectionInsets = UIEdgeInsetsMake(10, 10, 10, 10); 50     // 设置间距 51     water.spacing = 10; 52     // 设置有多少列 53     water.numberOfColumn = 3; 54     // 设置代理 55     water.delegate = self; 56      57     // 2.布局UICollectionView 58     self.collectionView = [[UICollectionView alloc] initWithFrame:self.view.frame collectionViewLayout:water]; 59     self.collectionView.delegate = self; 60     self.collectionView.dataSource = self; 61     self.collectionView.backgroundColor = [UIColor whiteColor]; 62     [self.view addSubview:self.collectionView]; 63      64 } 65  66 // 读取数据 67 - (void)loadData { 68     69     // 第一步:获取文件路径 70     NSString *filePath = [[NSBundle mainBundle] pathForResource:@"Data" ofType:@"json"]; 71     // 第二步:根据路径读取数据,转为NSData对象 72     NSData *data = [NSData dataWithContentsOfFile:filePath]; 73     // 第三步:根据json格式解析数据 74     NSArray *dataArray = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil]; 75      76 //    NSLog(@"%@", dataArray); 77     // 第四步:遍历数组,将数据转为model对象 78     for (NSDictionary *dict in dataArray) { 79          80         // 创建model对象 81         DataModel *model = [[DataModel alloc] init]; 82         // 使用KVC给model赋值 83         [model setValuesForKeysWithDictionary:dict]; 84          85         // 将model添加到大数组中 86         [self.allDataArray addObject:model]; 87     } 88 //    NSLog(@"%@", self.allDataArray); 89 } 90  91 // 设置分区个数 92 - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { 93     return 1; 94 } 95 // 设置每个分区的item个数 96 - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { 97     return self.allDataArray.count; 98 } 99 100 // 返回每一个item的cell对象101 - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {102     103     RootCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"cell" forIndexPath:indexPath];104     105     // 设置cell数据106     107     DataModel *model = self.allDataArray[indexPath.row];108     NSURL *url = [NSURL URLWithString:model.thumbURL];109     [cell.photoView sd_setImageWithURL:url];110     cell.backgroundColor = [UIColor orangeColor];111     return cell;112 }113 114 // 实现代理方法返回每一个item的高度115 - (CGFloat)heightForItemAtIndexPath:(NSIndexPath *)indexPath {116     117     DataModel *model = self.allDataArray[indexPath.row];118     CGFloat width = ([UIScreen mainScreen].bounds.size.width - 40) / 3;119     // 计算item高度120     CGFloat height = model.height / model.width * width;121     return height;122     123 }124 125 @end

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

 

RootCell.h

1 #import <UIKit/UIKit.h>2 3 @interface RootCell : UICollectionViewCell4 5 @property (nonatomic, strong) UIImageView *photoView;6 7 @end

RootCell.m

 1 #import "RootCell.h" 2  3 @implementation RootCell 4  5 - (instancetype)initWithFrame:(CGRect)frame { 6     self = [super initWithFrame:frame]; 7     if (self) { 8         self.photoView = [[UIImageView alloc] init]; 9         [self.contentView addSubview:self.photoView];10     }11     return self;12 }13 14 - (void)layoutSubviews {15     16     self.photoView.frame = self.bounds;17     18 }19 20 @end

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

 

WaterFlowLayout.h

 1 #import <UIKit/UIKit.h> 2  3 @protocol WaterFlowLayoutDelegate <NSObject> 4  5 // 返回每一个item的高度 6 - (CGFloat)heightForItemAtIndexPath:(NSIndexPath *)indexPath; 7  8 @end 9 10 @interface WaterFlowLayout : UICollectionViewLayout11 12 // item的大小,需要根据这个获取宽度13 @property (nonatomic, assign) CGSize itemSize;14 15 // 内边距的设置16 @property (nonatomic, assign) UIEdgeInsets sectionInsets;17 18 // item的间距(这里水平方向和垂直方向的间距一样)19 @property (nonatomic, assign) CGFloat spacing;20 21 // 列数22 @property (nonatomic, assign) NSInteger numberOfColumn;23 24 // 设置代理,用于获取item的高度25 @property (nonatomic, weak) id<WaterFlowLayoutDelegate>delegate;26 27 @end

WaterFlowLayout.m

  1 #import "WaterFlowLayout.h"  2   3 @interface WaterFlowLayout ()  4   5 // 声明私有属性  6 // 保存一共有多少个item  7 @property (nonatomic, assign) NSInteger numberOfItems;  8   9 // 保存计算好的每一个item的信息 10 @property (nonatomic, strong) NSMutableArray *itemAttributes; 11  12 // 保存每一列的高度 13 @property (nonatomic, strong) NSMutableArray *columnHeights; 14  15  16 // 声明私有方法 17 // 找到当前最长列 18 - (NSInteger)indexOfHeightestColumn; 19  20 // 找到当前最短列 21 - (NSInteger)indexOfShortestColumn; 22  23 @end 24  25 @implementation WaterFlowLayout 26  27 // 懒加载 28 - (NSMutableArray *)itemAttributes { 29     if (!_itemAttributes) { 30         _itemAttributes = [NSMutableArray array]; 31     } 32     return _itemAttributes; 33 } 34  35 - (NSMutableArray *)columnHeights { 36     if (!_columnHeights) { 37         _columnHeights = [NSMutableArray array]; 38     } 39     return _columnHeights; 40 } 41  42 // 找到当前最长列 43 - (NSInteger)indexOfHeightestColumn { 44     // 记录最长列的下标 45     NSInteger index = 0; 46     // 记录最长列的高度 47     CGFloat length = 0; 48     for (int i = 0; i < self.columnHeights.count; i++) { 49         // 将数组中的对象转为基本数值 50         CGFloat currentLength = [self.columnHeights[i] floatValue]; 51         if (currentLength > length) { 52             length = currentLength; 53             index = i; 54         } 55     } 56     return index; 57 } 58  59 // 找到当前最短列 60 - (NSInteger)indexOfShortestColumn { 61     NSInteger index = 0; 62     CGFloat length = MAXFLOAT; 63     for (int i = 0; i < self.columnHeights.count; i++) { 64          65         CGFloat currentLength = [self.columnHeights[i] floatValue]; 66         if (currentLength < length) { 67             length = currentLength; 68             index = i; 69         } 70     } 71     return index; 72 } 73  74 // 接下来重写三个方法 75  76 // 准备布局,在这里计算每个item的frame 77 - (void)prepareLayout { 78      79     // 拿到一共有多少个item 80     self.numberOfItems = [self.collectionView numberOfItemsInSection:0]; 81     // 每一列添加一个top高度 82     for (int i = 0; i < self.numberOfColumn; i++) { 83         // @() NSNumber字面量创建对象 84         self.columnHeights[i] = @(self.sectionInsets.top); 85     } 86      87     // 依次为每个item设置位置信息,并存储在数组中 88     for (int i = 0; i < self.numberOfItems; i++) { 89          90         // 1.找到最短列的下标 91         NSInteger shortestIndex = [self indexOfShortestColumn]; 92         // 2.计算X 目标X = 内边距左间距 + (宽 + item间距)*最短列下标 93         CGFloat detalX = self.sectionInsets.left + shortestIndex * (self.itemSize.width + self.spacing); 94         // 3.找到最短列的高度 95         CGFloat height = [self.columnHeights[shortestIndex] floatValue]; 96         // 4.计算Y 97         CGFloat detalY = height + self.spacing; 98         // 5.创建indexPath 99         NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];100         101         // 6.调用代理方法计算高度102         CGFloat itemHeight = 0;103         if (_delegate && [_delegate respondsToSelector:@selector(heightForItemAtIndexPath:)]) {104             itemHeight = [_delegate heightForItemAtIndexPath:indexPath];105         }106         107         // 定义保存位置信息的对象108         UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];109         110         // 7.生成frame111         attributes.frame = CGRectMake(detalX, detalY, self.itemSize.width, itemHeight);112         // 8.将位置信息添加到数组中113         [self.itemAttributes addObject:attributes];114         115         // 9.更新这一列的高度116         self.columnHeights[shortestIndex] = @(detalY + itemHeight);117     }118     119 }120 121 // 返回UICollectionView的大小122 - (CGSize)collectionViewContentSize {123     124     // 求最高列的下标125     NSInteger heightest = [self indexOfHeightestColumn];126     // 最高列的高度127     CGFloat height = [self.columnHeights[heightest] floatValue];128     // 拿到collectionView的原始大小129     CGSize size = self.collectionView.frame.size;130     size.height = height + self.sectionInsets.bottom;131     132     return size;133 }134 135 // 返回每一个item的位置信息136 - (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {137     return self.itemAttributes;138 }139 140 @end

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

 

DataModel.h

 1 #import <Foundation/Foundation.h> 2  3 @interface DataModel : NSObject 4  5 @property (nonatomic, copy) NSString *thumbURL; 6  7 @property (nonatomic, assign) float width; 8  9 @property (nonatomic, assign) float height;10 11 12 @end

DataModel.m

 1 #import "DataModel.h" 2  3 @implementation DataModel 4  5 // 防崩 6 - (void)setValue:(id)value forUndefinedKey:(NSString *)key { 7      8 } 9 10 @end

 

0 0