瀑布流

简单的瀑布流实现,包含单元格的重用,有问题希望高手指出

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

 cell的.h文件 

@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的数组中,


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值