本篇文章将会仿照苹果系统提供的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开发的问题!欢迎下方留言!!!或者邮件lieryangios@126.com 虽然我不一定能够解答出来,但是我会请教iOS开发高手!!!解答您的问题!!!

瀑布流封装(仿写UITableView)的更多相关文章

  1. iOS 瀑布流封装

    代码地址如下:http://www.demodashi.com/demo/12284.html 一.效果预览 功能描述:WSLWaterFlowLayout 是在继承于UICollectionView ...

  2. android 瀑布流效果(仿蘑菇街)

    我们还是来看一款示例:(蘑菇街)           看起来很像我们的gridview吧,不过又不像,因为item大小不固定的,看起来是不是别有一番风味,确实如此.就如我们的方角图形,斯通见惯后也就出 ...

  3. iOS横向瀑布流的封装

    前段时间, 做一个羡慕, 需要使用到瀑布流! 说道瀑布流, 或许大家都不陌生, 瀑布流的实现也有很多种! 从scrollView 到 tableView 书写的瀑布流, 然后再到2012年iOS6 苹 ...

  4. iOS开发:一个瀑布流的设计与实现(已实现缓存池功能,该功能使得瀑布流cell可以循环利用)

    一个瀑布流的实现有三种方式: 继承自UIScrollView,仿写UITableView的dataSource和delegate,创造一个缓存池用来实现循环利用cell 写多个UITableview( ...

  5. iOS 瀑布流之栅格布局

    代码地址如下:http://www.demodashi.com/demo/14760.html 一 .效果预览 二.确定需求 由下面的需求示意图可知模块的最小单位是正方形,边长是屏幕宽除去边距间隔后的 ...

  6. iOS动画效果集合、 通过摄像头获取心率、仿淘宝滑动样式、瀑布流、分类切换布局等源码

    iOS精选源码 动画知识运用及常见动画效果收集 较为美观的多级展开列表 MUImageCache -简单轻量的图片缓存方案 iOS 瀑布流之栅格布局 一用就上瘾的JXCategoryView iOS ...

  7. ASP.NET仿新浪微博下拉加载更多数据瀑布流效果

    闲来无事,琢磨着写点东西.貌似页面下拉加载数据,瀑布流的效果很火,各个网站都能见到各式各样的展示效果,原理大同小异.于是乎,决定自己写一写这个效果,希望能给比我还菜的菜鸟们一点参考价值. 在开始之前, ...

  8. UICollectionView 很简单的写个瀑布流

    你项目中要用到它吗? 可能会在你的项目中用到这玩意,最近也是要用就简单的写了一个 Demo.没多少代码,就不放Git了,下面会详细点的说说代码的,要还有什么问题的小伙伴可以直接Q我,也可以把Demo发 ...

  9. android瀑布流效果(仿蘑菇街)

    Android 转载分享(10)  我们还是来看一款示例:(蘑菇街)           看起来很像我们的gridview吧,不过又不像,因为item大小不固定的,看起来是不是别有一番风味,确实如此. ...

随机推荐

  1. 2014-10-31 NOIP模拟赛

        10.30 NOIp  模拟赛   时间 空间 测试点 评测方式 挖掘机(dig.*) 1s 256M 10 传统 黑红树(brtree.*) 2s 256M 10 传统 藏宝图(treas. ...

  2. uoj#399. 【CTSC2018】假面(概率期望)

    传送门 记\(p_{i,j}\)为\(i\)还剩\(j\)滴血的概率,那么\(i\)最后血量的期望就是\[E_i=\sum_{j=0}^{m_i}j\times p_{i,j}\] 然后\(p\)数组 ...

  3. 剑指Offer的学习笔记(C#篇)-- 替换空格

    题目描述 请实现一个函数,将一个字符串中的每个空格替换成“%20”.例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy. 一 . 自己的想法 老实说,貌 ...

  4. 【SpringBoot】编写一个自己的Starter

    一.什么是Starter? 在开发过程中我们就经常使用到各种starter,比如mybatis-spring-boot-starter,只需要进行简单的配置即可使用,就像一个插件非常方便.这也是Spr ...

  5. Vue中如何将数据传递到下一个页面(超级简单哒)

    先展示效果:注意URL中值是有变化的 一:在goodslist.vue文件夹绑定 <router-link :to="'/goodsinfo/'+subitem.artID" ...

  6. Codeforces 1168C(二进制、dp)

    要点 '&'操作暗示二进制上按位思考 对于y为1的位,要求x和y之间要至少有两个此位为1的(包含x.y),这样&起来才不是0.而这些位中只要存在一个是ok的即可 dp去求每个x的每个位 ...

  7. hdu1175-连连看(dfs)

    一个一个走,记录方向改变了几次,不能超过两次,两次如果还没到终点return: #include<cstdio> #include<string.h> #define inf ...

  8. Spark Mllib里决策树回归分析使用.rootMeanSquaredError方法计算出以RMSE来评估模型的准确率(图文详解)

    不多说,直接上干货! Spark Mllib里决策树二元分类使用.areaUnderROC方法计算出以AUC来评估模型的准确率和决策树多元分类使用.precision方法以precision来评估模型 ...

  9. 开发工具~nuget配置本地源

    我们在本地部署了自己的nuget服务器,有时可以需要用到nuget restore命令去恢复包包,它会从下面的nuget.config里读你的配置源信息,就是在这里,我们要把内测的nuget服务器路径 ...

  10. windows 用VMware创建linux虚拟机,安装操作系统CentOS7.2

    1.按照向导创建虚拟机 以下是安装虚拟机的步骤,没有写的直接下一步 [1]主页-创建新虚拟机 [2]选择 自定义(高级) [3]选择稍后安装操作系统 [4]给虚拟机命名并指定所在位置 [5]给处理器配 ...