本篇文章将会仿照苹果系统提供的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 - ) * 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 = ; i<numberOfColumns; i++) {
maxYOfColumns[i] = 0.0;
} // 计算所有cell的frame
for (int i = ; i<numberOfCells; i++) {
// cell处在第几列(最短的一列)
NSUInteger cellColumn = ;
// cell所处那列的最大Y值(最短那一列的最大Y值)
CGFloat maxYOfCellColumn = maxYOfColumns[cellColumn];
// 求出最短的一列
for (int j = ; 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 = ;
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[];
for (int j = ; j<numberOfColumns; j++) {
if (maxYOfColumns[j] > contentH) {
contentH = maxYOfColumns[j];
}
}
contentH += bottomM;
self.contentSize = CGSizeMake(, contentH);
} /**
* 当UIScrollView滚动的时候也会调用这个方法
*/
- (void)layoutSubviews {
[super layoutSubviews]; // 向数据源索要对应位置的cell
NSUInteger numberOfCells = self.cellFrames.count;
for (int i = ; 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 ;
} - (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 + arc4random_uniform();
}
@end
GitHub中Demo地址: https://github.com/lieryang/Waterflow
感觉可以的话可以点个小心心❤️ 呦!
更多内容--> 博客导航 每周一篇哟!!!
有任何关于iOS开发的问题!欢迎下方留言!!!或者邮件[email protected] 虽然我不一定能够解答出来,但是我会请教iOS开发高手!!!解答您的问题!!!