瀑布流封装(仿写UITableView)
本篇文章将会仿照苹果系统提供的UITableView类,封装一个瀑布流效果的控件!!!
该控件和系统的UITableView是相同级别的 (继承自系统的UIScrollView)
GitHub中Demo地址: https://github.com/lieryang/Waterflow
#pragma mark - EYWaterflowView
EYWaterflowView.h
#import <UIKit/UIKit.h> typedef enum { EYWaterflowViewMarginTypeTop, EYWaterflowViewMarginTypeBottom, EYWaterflowViewMarginTypeLeft, EYWaterflowViewMarginTypeRight, EYWaterflowViewMarginTypeColumn, // 每一列 EYWaterflowViewMarginTypeRow, // 每一行 } EYWaterflowViewMarginType; @class EYWaterflowView, EYWaterflowViewCell; @protocol EYWaterflowViewDataSource <NSObject> @required /** 一共有多少个数据 @param waterflowView 瀑布流控件 @return 数据的个数 */ - (NSUInteger)numberOfCellsInWaterflowView:(EYWaterflowView *)waterflowView; /** 对应index位置对应的cell @param waterflowView 瀑布流控件 @param index 下标 @return 对应的cell */ - (EYWaterflowViewCell *)waterflowView:(EYWaterflowView *)waterflowView cellAtIndex:(NSUInteger)index; @optional /** 一共有多少列 @param waterflowView 瀑布流控件 @return 列的个数 */ - (NSUInteger)numberOfColumnsInWaterflowView:(EYWaterflowView *)waterflowView; @end @protocol EYWaterflowViewDelegate <UIScrollViewDelegate> @optional /** index位置cell对应的高度 @param waterflowView 瀑布流控件 @param index 下标 @return 对应的高度 */ - (CGFloat)waterflowView:(EYWaterflowView *)waterflowView heightAtIndex:(NSUInteger)index; /** index位置的cell @param waterflowView 瀑布流控件 @param index 选中的下标 */ - (void)waterflowView:(EYWaterflowView *)waterflowView didSelectAtIndex:(NSUInteger)index; /** 设置间距 @param waterflowView 瀑布流控件 @param type 瀑布流控件的间距(枚举) @return 对应方向的间距 */ - (CGFloat)waterflowView:(EYWaterflowView *)waterflowView marginForType:(EYWaterflowViewMarginType)type; @end @interface EYWaterflowView : UIScrollView @property (nonatomic, weak) id<EYWaterflowViewDataSource> dataSource; @property (nonatomic, weak) id<EYWaterflowViewDelegate> delegate; /** 刷新数据(只要调用这个方法,会重新向数据源和代理发送请求,请求数据) */ - (void)reloadData; /** cell的宽度 @return cell的宽度 */ - (CGFloat)cellWidth; /** 根据标识去缓存池查找可循环利用的cell @param identifier 重用标识符 @return 对应的cell */ - (__kindof EYWaterflowViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier; @end
EYWaterflowView.m
#import "EYWaterflowView.h" #import "EYWaterflowViewCell.h" #define EYWaterflowViewDefaultCellH 70 #define EYWaterflowViewDefaultMargin 8 #define EYWaterflowViewDefaultNumberOfColumns 3 @interface EYWaterflowView() /** * 所有cell的frame数据 */ @property (nonatomic, strong) NSMutableArray *cellFrames; /** * 正在展示的cell */ @property (nonatomic, strong) NSMutableDictionary *displayingCells; /** * 缓存池(用Set,存放离开屏幕的cell) */ @property (nonatomic, strong) NSMutableSet *reusableCells; @end @implementation EYWaterflowView @synthesize delegate = _delegate; //即将显示到父控件上面 - (void)willMoveToSuperview:(UIView *)newSuperview { [self reloadData]; } #pragma mark - 公共接口 /** * cell的宽度 */ - (CGFloat)cellWidth { // 总列数 NSUInteger numberOfColumns = [self numberOfColumns]; CGFloat leftM = [self marginForType:EYWaterflowViewMarginTypeLeft]; CGFloat rightM = [self marginForType:EYWaterflowViewMarginTypeRight]; CGFloat columnM = [self marginForType:EYWaterflowViewMarginTypeColumn]; return (self.bounds.size.width - leftM - rightM - (numberOfColumns - 1) * columnM) / numberOfColumns; } /** * 刷新数据 */ - (void)reloadData { // 清空之前的所有数据 // 移除正在正在显示cell [self.displayingCells.allValues makeObjectsPerformSelector:@selector(removeFromSuperview)]; [self.displayingCells removeAllObjects]; [self.cellFrames removeAllObjects]; [self.reusableCells removeAllObjects]; // cell的总数 NSUInteger numberOfCells = [self.dataSource numberOfCellsInWaterflowView:self]; // 总列数 NSUInteger numberOfColumns = [self numberOfColumns]; // 间距 CGFloat topM = [self marginForType:EYWaterflowViewMarginTypeTop]; CGFloat bottomM = [self marginForType:EYWaterflowViewMarginTypeBottom]; CGFloat leftM = [self marginForType:EYWaterflowViewMarginTypeLeft]; CGFloat columnM = [self marginForType:EYWaterflowViewMarginTypeColumn]; CGFloat rowM = [self marginForType:EYWaterflowViewMarginTypeRow]; // cell的宽度 CGFloat cellW = [self cellWidth]; // 用一个C语言数组存放所有列的最大Y值 CGFloat maxYOfColumns[numberOfColumns]; for (int i = 0; i<numberOfColumns; i++) { maxYOfColumns[i] = 0.0; } // 计算所有cell的frame for (int i = 0; i<numberOfCells; i++) { // cell处在第几列(最短的一列) NSUInteger cellColumn = 0; // cell所处那列的最大Y值(最短那一列的最大Y值) CGFloat maxYOfCellColumn = maxYOfColumns[cellColumn]; // 求出最短的一列 for (int j = 1; j<numberOfColumns; j++) { if (maxYOfColumns[j] < maxYOfCellColumn) { cellColumn = j; maxYOfCellColumn = maxYOfColumns[j]; } } // 询问代理i位置的高度 CGFloat cellH = [self heightAtIndex:i]; // cell的位置 CGFloat cellX = leftM + cellColumn * (cellW + columnM); CGFloat cellY = 0; if (maxYOfCellColumn == 0.0) { // 首行 cellY = topM; } else { cellY = maxYOfCellColumn + rowM; } // 添加frame到数组中 CGRect cellFrame = CGRectMake(cellX, cellY, cellW, cellH); [self.cellFrames addObject:[NSValue valueWithCGRect:cellFrame]]; // 更新最短那一列的最大Y值 maxYOfColumns[cellColumn] = CGRectGetMaxY(cellFrame); } // 设置contentSize CGFloat contentH = maxYOfColumns[0]; for (int j = 1; j<numberOfColumns; j++) { if (maxYOfColumns[j] > contentH) { contentH = maxYOfColumns[j]; } } contentH += bottomM; self.contentSize = CGSizeMake(0, contentH); } /** * 当UIScrollView滚动的时候也会调用这个方法 */ - (void)layoutSubviews { [super layoutSubviews]; // 向数据源索要对应位置的cell NSUInteger numberOfCells = self.cellFrames.count; for (int i = 0; i<numberOfCells; i++) { // 取出i位置的frame CGRect cellFrame = [self.cellFrames[i] CGRectValue]; // 优先从字典中取出i位置的cell EYWaterflowViewCell *cell = self.displayingCells[@(i)]; // 判断i位置对应的frame在不在屏幕上(能否看见) if ([self isInScreen:cellFrame]) { // 在屏幕上 if (cell == nil) { cell = [self.dataSource waterflowView:self cellAtIndex:i]; cell.frame = cellFrame; [self addSubview:cell]; // 存放到字典中 self.displayingCells[@(i)] = cell; } } else { // 不在屏幕上 if (cell) { // 从scrollView和字典中移除 [cell removeFromSuperview]; [self.displayingCells removeObjectForKey:@(i)]; // 存放进缓存池 [self.reusableCells addObject:cell]; } } } } - (__kindof EYWaterflowViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier { __block EYWaterflowViewCell *reusableCell = nil; [self.reusableCells enumerateObjectsUsingBlock:^(EYWaterflowViewCell *cell, BOOL *stop) { if ([cell.reuseIdentifier isEqualToString:identifier]) { reusableCell = cell; *stop = YES; } }]; if (reusableCell) { // 从缓存池中移除 [self.reusableCells removeObject:reusableCell]; } return reusableCell; } #pragma mark - 私有方法 /** * 判断一个frame有无显示在屏幕上 */ - (BOOL)isInScreen:(CGRect)frame { return (CGRectGetMaxY(frame) > self.contentOffset.y) && (CGRectGetMinY(frame) < self.contentOffset.y + self.bounds.size.height); } /** * 间距 */ - (CGFloat)marginForType:(EYWaterflowViewMarginType)type { if ([self.delegate respondsToSelector:@selector(waterflowView:marginForType:)]) { return [self.delegate waterflowView:self marginForType:type]; } else { return EYWaterflowViewDefaultMargin; } } /** * 总列数 */ - (NSUInteger)numberOfColumns { if ([self.dataSource respondsToSelector:@selector(numberOfColumnsInWaterflowView:)]) { return [self.dataSource numberOfColumnsInWaterflowView:self]; } else { return EYWaterflowViewDefaultNumberOfColumns; } } /** * index位置对应的高度 */ - (CGFloat)heightAtIndex:(NSUInteger)index { if ([self.delegate respondsToSelector:@selector(waterflowView:heightAtIndex:)]) { return [self.delegate waterflowView:self heightAtIndex:index]; } else { return EYWaterflowViewDefaultCellH; } } #pragma mark - 事件处理 - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { if (![self.delegate respondsToSelector:@selector(waterflowView:didSelectAtIndex:)]) return; // 获得触摸点 UITouch *touch = [touches anyObject]; CGPoint point = [touch locationInView:self]; __block NSNumber *selectIndex = nil; [self.displayingCells enumerateKeysAndObjectsUsingBlock:^(id key, EYWaterflowViewCell *cell, BOOL *stop) { if (CGRectContainsPoint(cell.frame, point)) { selectIndex = key; *stop = YES; } }]; if (selectIndex) { [self.delegate waterflowView:self didSelectAtIndex:selectIndex.unsignedIntegerValue]; } } #pragma mark - 懒加载 - (NSMutableArray *)cellFrames { if (_cellFrames == nil) { _cellFrames = [NSMutableArray array]; } return _cellFrames; } - (NSMutableDictionary *)displayingCells { if (_displayingCells == nil) { _displayingCells = [NSMutableDictionary dictionary]; } return _displayingCells; } - (NSMutableSet *)reusableCells { if (_reusableCells == nil) { _reusableCells = [NSMutableSet set]; } return _reusableCells; } @end
#pragma mark - EYWaterflowViewCell
EYWaterflowViewCell.h
#import <UIKit/UIKit.h> @interface EYWaterflowViewCell : UIView //重用标识符 @property (nonatomic, readonly, copy) NSString *reuseIdentifier; - (__kindof EYWaterflowViewCell *)initWithReuseIdentifier:(NSString *)reuseIdentifier; @end
EYWaterflowViewCell.m
#import "EYWaterflowViewCell.h" @interface EYWaterflowViewCell() @property (nonatomic, readwrite, copy) NSString *reuseIdentifier; @end @implementation EYWaterflowViewCell - (__kindof EYWaterflowViewCell *)initWithReuseIdentifier:(NSString *)reuseIdentifier { self = [super init]; if (self) { self.reuseIdentifier = reuseIdentifier; } return self; } @end
#pragma mark - 具体使用
#import "ViewController.h" #import "EYWaterflowView.h" #include "EYWaterflowViewCell.h" @interface ViewController () <EYWaterflowViewDataSource, EYWaterflowViewDelegate> @property (weak, nonatomic) EYWaterflowView * waterflowView; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; EYWaterflowView * waterflowView = [[EYWaterflowView alloc] initWithFrame:self.view.bounds]; waterflowView.dataSource = self; waterflowView.delegate = self; [self.view addSubview:waterflowView]; self.waterflowView = waterflowView; } #pragma mark - EYWaterflowViewDataSource - (NSUInteger)numberOfCellsInWaterflowView:(EYWaterflowView *)waterflowView
{ return 100; } - (EYWaterflowViewCell *)waterflowView:(EYWaterflowView *)waterflowView cellAtIndex:(NSUInteger)index
{ static NSString * cellID = @"cellID"; EYWaterflowViewCell * cell = [waterflowView dequeueReusableCellWithIdentifier:cellID]; if (cell == nil) { cell = [[EYWaterflowViewCell alloc] initWithReuseIdentifier:cellID]; cell.backgroundColor = [UIColor redColor]; } return cell; } #pragma mark - EYWaterflowViewDelegate - (CGFloat)waterflowView:(EYWaterflowView *)waterflowView heightAtIndex:(NSUInteger)index { return 100 + arc4random_uniform(100); } @end
GitHub中Demo地址: https://github.com/lieryang/Waterflow
感觉可以的话可以点个小心心❤️ 呦!
更多内容--> 博客导航 每周一篇哟!!!
有任何关于iOS开发的问题!欢迎下方留言!!!或者邮件lieryangios@126.com 虽然我不一定能够解答出来,但是我会请教iOS开发高手!!!解答您的问题!!!
程序猿 CoderEYLee https://github.com/lieryang