瀑布流封装(仿写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
View Code

 

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
View Code

 

 

#pragma mark - EYWaterflowViewCell

 

EYWaterflowViewCell.h

 

#import <UIKit/UIKit.h>

@interface EYWaterflowViewCell : UIView

//重用标识符
@property (nonatomic, readonly, copy) NSString *reuseIdentifier;

- (__kindof EYWaterflowViewCell *)initWithReuseIdentifier:(NSString *)reuseIdentifier;

@end
View Code

 

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
View Code

 

  

#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开发高手!!!解答您的问题!!!

posted on 2018-01-18 11:35  人生为代码而活  阅读(716)  评论(0编辑  收藏  举报

导航