简单的瀑布流实现,包含单元格的重用,有问题希望高手指出
view的.h文件
#import <UIKit/UIKit.h>
@class SOPullFlowView ,SOPulFlowlViewCell;
#pragma mark --dataspurce
@protocol SOPullFlowViewDataSource <NSObject>
- (NSInteger)pullFlowView:(SOPullFlowView *)pullFlowView numberOfCellInSection:(NSInteger)section;
- (SOPulFlowlViewCell *)pullFlowView:(SOPullFlowView *)pullFlowView cellAtIndexPath:(NSIndexPath *)indexpath;
@optional
- (NSInteger)numberOfSectionInPullFlowView:(SOPullFlowView *)pullFlowView;
@end
#pragma mark --delegate
@protocol SOPullFlowViewDelegate <NSObject,UIScrollViewDelegate>
@required
- (CGFloat)pullFlowView:(SOPullFlowView *)pullFlowView heighForCellAtIndexPath:(NSIndexPath *)indexPath;
@optional
/**
* 瀑布流的列数
*/
- (NSInteger)numberOfColumnInPullFlowView:(SOPullFlowView *)pullFlowView;
@end
@interface SOPullFlowView : UIScrollView
@property (nonatomic, weak) id<SOPullFlowViewDataSource> dataSource;
@property (nonatomic, weak) id<SOPullFlowViewDelegate> delegate;
#pragma mark 查询可重用单元格
- (SOPulFlowlViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier;
@end
view的.m文件
<pre name="code" class="objc">#import "SOPullFlowView.h"
#import "UIView+Extension.h"
#import "SOPulFlowlViewCell.h"
#define SOScreenW [UIScreen mainScreen].bounds.size.width
@interface SOPullFlowView ()
//瀑布流的列数
@property (nonatomic, assign) NSInteger numberOfColumn;
//cell的总个数
@property (nonatomic, assign) NSInteger numberOfCells;
//瀑布流的左右边缘的间距
@property (nonatomic, assign) CGFloat borderMargin;
//瀑布流cell之间的左右间距
@property (nonatomic, assign) CGFloat cellMargin;
//cell之间的上下间距
@property (nonatomic, assign) CGFloat rowMargin;
//保存重用cell
@property (nonatomic, strong) NSMutableSet *reuseCell;
//保存当前屏幕上的cell
@property (nonatomic, strong) NSMutableDictionary *screenCellDict;
//保存所有cell的indexpath
@property (nonatomic, strong) NSMutableArray *indesPath;
//保存所有cell的frame
@property (nonatomic, strong) NSMutableArray *cellsFrame;
@end
@implementation SOPullFlowView
@dynamic delegate;
-(void)setFrame:(CGRect)frame{
// self.frame = frame;
[super setFrame:frame];
[self reloadDate];
}
-(SOPulFlowlViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier{
SOPulFlowlViewCell *cell = [self.reuseCell anyObject];
if(cell){
if (![cell.reuseIdentifer isEqualToString:identifier]) return nil;
[_reuseCell removeObject:cell];
}
return cell;
}
- (void)generateCacheData{
NSInteger cellCount = self.numberOfCells;
if (self.screenCellDict){
[self.screenCellDict removeAllObjects];
}else{
self.screenCellDict = [NSMutableDictionary dictionary];
}
if (self.indesPath){
[self.indesPath removeAllObjects];
}else{
self.indesPath = [NSMutableArray arrayWithCapacity:cellCount];
}
for (NSInteger i = 0; i < cellCount; i++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
[self.indesPath addObject:indexPath];
}
if (self.cellsFrame){
[self.cellsFrame removeAllObjects];
}else{
self.cellsFrame = [NSMutableArray array];
}
if (self.reuseCell){
[self.reuseCell removeAllObjects];
}else{
self.reuseCell = [NSMutableSet set];
}
}
//重新计算每个cell的frame保存在一个数组,并设置contentSize
- (void)resetView{
[self generateCacheData];
CGFloat cellW = (self.bounds.size.width - 2 * self.borderMargin - (self.numberOfColumn - 1) * self.cellMargin) / self.numberOfColumn;
CGFloat currentMaxY[_numberOfColumn];
for (NSInteger i = 0; i < self.numberOfColumn; i++){
currentMaxY[i] = 0;
}
for (NSIndexPath *indexPath in self.indesPath){
CGFloat cellX = self.borderMargin + (cellW + self.cellMargin) * [self minIndexInCurrentMaxY:currentMaxY]; ;
CGFloat cellY = currentMaxY[[self minIndexInCurrentMaxY:currentMaxY]] + self.rowMargin;
CGFloat cellH;
if ([self.delegate respondsToSelector:@selector(pullFlowView:heighForCellAtIndexPath:)]){
cellH = [self.delegate pullFlowView:self heighForCellAtIndexPath:indexPath];
}else{
NSAssert(nil, @"请设置cell的高度");
}
CGRect cellFrame = CGRectMake(cellX, cellY, cellW, cellH);
[self.cellsFrame addObject:[NSValue valueWithCGRect:cellFrame]];
currentMaxY[[self minIndexInCurrentMaxY:currentMaxY]] = CGRectGetMaxY(cellFrame);
}
CGFloat contentH = 0;
for (NSInteger i = 0; i< _numberOfColumn; i ++){
if (currentMaxY[i] > contentH) contentH = currentMaxY[i];
}
self.contentSize = CGSizeMake(0, contentH + 30);
}
//获得下一个cell应该插入的列数
- (NSInteger)minIndexInCurrentMaxY:(CGFloat *)currentMaxY{
CGFloat minY = currentMaxY[0];
NSInteger minIndex = 0;
for (NSInteger i = 0; i < self.numberOfColumn; i ++) {
if (minY > currentMaxY[i]){
minY = currentMaxY[i];
minIndex = i;
}
}
return minIndex;
}
//重新布局子控件,设置frame
- (void)layoutSubviews{
[super layoutSubviews];
NSInteger index = 0;
for (NSIndexPath *indexPath in self.indesPath) {
SOPulFlowlViewCell *cell = self.screenCellDict[indexPath];
if (cell == nil){
CGRect cellFrame = [self.cellsFrame[index] CGRectValue];
if ([self isInScreenWithFrame:cellFrame]){
cell = [self.dataSource pullFlowView:self cellAtIndexPath:indexPath];
[cell setFrame:cellFrame];
[self addSubview:cell];
[self.screenCellDict setObject:cell forKey:indexPath];
}
}else{
if (![self isInScreenWithFrame:cell.frame]){
[cell removeFromSuperview];
[self.screenCellDict removeObjectForKey:indexPath];
[self.reuseCell addObject:cell];
}
}
index ++;
}
}
- (BOOL)isInScreenWithFrame:(CGRect)cellFrame{
return ((cellFrame.origin.y + cellFrame.size.height > self.contentOffset.y) &&
(cellFrame.origin.y < self.contentOffset.y + self.bounds.size.height));
}
- (void)reloadDate{
if (self.numberOfCells == 0) return;
//重新计算frame
[self resetView];
}
-(NSInteger)numberOfColumn{
_numberOfColumn = 3;
if ([self.delegate respondsToSelector:@selector(numberOfColumnInPullFlowView:)]){
_numberOfColumn = [self.delegate numberOfColumnInPullFlowView:self];
}
return _numberOfColumn;
}
-(NSInteger)numberOfCells{
_numberOfCells = [self.dataSource pullFlowView:self numberOfCellInSection:0];
return _numberOfCells;
}
#warning 默认值设置的15,可以.h代理中给相应的方法来设置对应的值
-(CGFloat)borderMargin{
_borderMargin = 15;
return _borderMargin;
}
-(CGFloat)cellMargin{
_cellMargin = 15;
return _cellMargin;
}
-(CGFloat)rowMargin{
_rowMargin = 15;
return _rowMargin;
}
@end
@interface SOPulFlowlViewCell : UIView
@property (nonatomic, weak) UILabel *sortLabel;
@property (nonatomic, strong) NSString *reuseIdentifer;
- (instancetype)initWithReuseIdentifer:(NSString *)reuseIdentifer;
@end
cell的.m文件
#import "SOPulFlowlViewCell.h"
@implementation SOPulFlowlViewCell
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
// Drawing code
}
*/
- (instancetype)init{
if (self = [super init]){
UILabel *label = [UILabel new];
self.sortLabel = label;
[self addSubview:label];
label.frame = CGRectMake(10, 10, 40, 40);
label.font = [UIFont boldSystemFontOfSize:20];
}
return self;
}
- (instancetype)initWithReuseIdentifer:(NSString *)reuseIdentifer{
self = [super init];
if (self){
self.reuseIdentifer = reuseIdentifer;
UILabel *label = [UILabel new];
self.sortLabel = label;
[self addSubview:label];
label.frame = CGRectMake(10, 10, 40, 40);
label.font = [UIFont boldSystemFontOfSize:20];
}
return self;
}
@end
在viewCOntroller中的调用
#import "ViewController.h"
#import "SOPullFlowView.h"
#import "SOPulFlowlViewCell.h"
@interface ViewController ()<SOPullFlowViewDataSource,SOPullFlowViewDelegate>
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
SOPullFlowView *view = [[SOPullFlowView alloc] initWithFrame:self.view.bounds];
self.view = view;
view.backgroundColor = [UIColor lightGrayColor];
view.dataSource = self;
view.delegate = self;
}
-(CGFloat)pullFlowView:(SOPullFlowView *)pullFlowView heighForCellAtIndexPath:(NSIndexPath *)indexPath{
return arc4random_uniform(50) + 200;
}
-(NSInteger)pullFlowView:(SOPullFlowView *)pullFlowView numberOfCellInSection:(NSInteger)section{
return 100;
}
- (SOPulFlowlViewCell *)pullFlowView:(SOPullFlowView *)pullFlowView cellAtIndexPath:(NSIndexPath *)indexpath{
SOPulFlowlViewCell *cell = [pullFlowView dequeueReusableCellWithIdentifier:@"123"];
if (!cell){
cell = [[SOPulFlowlViewCell alloc] initWithReuseIdentifer:@"123"];
}
cell.sortLabel.text = [NSString stringWithFormat:@"%ld",indexpath.row];
cell.backgroundColor = [UIColor colorWithRed:arc4random_uniform(255) / 255.0 green:arc4random_uniform(255) / 255.0 blue:arc4random_uniform(255) / 255.0 alpha:1.0];
return cell;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
思路:
实现瀑布流参考了tableView的设计思路,首先是在数据源里面确定view的section和cell的个数,以及每个cell显示的内容,当然和tableVIew一样,view的section为可选额dataSource
确定了数据源之后,在代理里面设置每个cell的高度,因为瀑布流的高度不同才叫瀑布流,如果每个cell的高度相同,那么就叫collectionView了,确定了每cell的高度,再刷新数据的时候一次性的计算完所有cell的frame,按照cell的顺序保存在一个数组里面,在计算完cell的frame之后,计算view的contentOfSize,保证view可以滚动.
重写setframe方法,在该方法中刷新数据,否则后面的数据不会被调用,在刷新数据的方法中清空用来保存数据的属性,没有初始化的进行初始化,
在layoutSubviews中,首先从保存屏幕上当前的cell的字典里面找对应index path的cell,如果没有这个cell,那么判断这个cell是否需要显示,如果需要显示,则问数据源cell的内容,如果cell有内容,需要判断这个cell是否还在屏幕上显示,如果不在屏幕上显示,从他的父控件中移除,从保存正在显示cell的字典中移除,加入到从用cell的NSMutableSet的数组中,