• 瀑布流封装(仿写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开发高手!!!解答您的问题!!!

  • 相关阅读:
    服务注册中心之Eureka使用
    微服务之服务注册中心
    Idea热部署功能
    微服务Cloud整体聚合工程创建过程
    微服务架构理论&SpringCloud
    关于母函数
    HDU 1028(母函数)整数划分
    1021 FIBERNACI
    1019
    1014 巧妙的gcd 生成元
  • 原文地址:https://www.cnblogs.com/CoderEYLee/p/Object-C-0036.html
Copyright © 2020-2023  润新知