瀑布流封装(仿写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
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)的更多相关文章
- iOS 瀑布流封装
代码地址如下:http://www.demodashi.com/demo/12284.html 一.效果预览 功能描述:WSLWaterFlowLayout 是在继承于UICollectionView ...
- android 瀑布流效果(仿蘑菇街)
我们还是来看一款示例:(蘑菇街) 看起来很像我们的gridview吧,不过又不像,因为item大小不固定的,看起来是不是别有一番风味,确实如此.就如我们的方角图形,斯通见惯后也就出 ...
- iOS横向瀑布流的封装
前段时间, 做一个羡慕, 需要使用到瀑布流! 说道瀑布流, 或许大家都不陌生, 瀑布流的实现也有很多种! 从scrollView 到 tableView 书写的瀑布流, 然后再到2012年iOS6 苹 ...
- iOS开发:一个瀑布流的设计与实现(已实现缓存池功能,该功能使得瀑布流cell可以循环利用)
一个瀑布流的实现有三种方式: 继承自UIScrollView,仿写UITableView的dataSource和delegate,创造一个缓存池用来实现循环利用cell 写多个UITableview( ...
- iOS 瀑布流之栅格布局
代码地址如下:http://www.demodashi.com/demo/14760.html 一 .效果预览 二.确定需求 由下面的需求示意图可知模块的最小单位是正方形,边长是屏幕宽除去边距间隔后的 ...
- iOS动画效果集合、 通过摄像头获取心率、仿淘宝滑动样式、瀑布流、分类切换布局等源码
iOS精选源码 动画知识运用及常见动画效果收集 较为美观的多级展开列表 MUImageCache -简单轻量的图片缓存方案 iOS 瀑布流之栅格布局 一用就上瘾的JXCategoryView iOS ...
- ASP.NET仿新浪微博下拉加载更多数据瀑布流效果
闲来无事,琢磨着写点东西.貌似页面下拉加载数据,瀑布流的效果很火,各个网站都能见到各式各样的展示效果,原理大同小异.于是乎,决定自己写一写这个效果,希望能给比我还菜的菜鸟们一点参考价值. 在开始之前, ...
- UICollectionView 很简单的写个瀑布流
你项目中要用到它吗? 可能会在你的项目中用到这玩意,最近也是要用就简单的写了一个 Demo.没多少代码,就不放Git了,下面会详细点的说说代码的,要还有什么问题的小伙伴可以直接Q我,也可以把Demo发 ...
- android瀑布流效果(仿蘑菇街)
Android 转载分享(10) 我们还是来看一款示例:(蘑菇街) 看起来很像我们的gridview吧,不过又不像,因为item大小不固定的,看起来是不是别有一番风味,确实如此. ...
随机推荐
- Spring IOC 的源码分析
刚学习Spring的时候,印象最深的就是 DispatcherServlet,所谓的中央调度器,我也尝试从这个万能胶这里找到入口 configureAndRefreshWebApplicationCo ...
- Docker 安装部署RabbitMQ
获查询镜像 docker search rabbitmq:management 可以看到如下结果: 获取镜像 docker pull rabbitmq:management 运行镜像 docker r ...
- 从各处收集的switch语句
重构之重复代码: 1.(重复代码是)语义一致的逻辑 反例:语义一致的逻辑产生了多个实体 缺点:如果你为语义一致的逻辑产生了多个实体,那么当需要修改这个逻辑时,你必须保证同时修改所有的实体,并确保它们是 ...
- SWT的基本组件使用
1按钮组件(Button) (1)Button组件常用样式 SWT.PUSH按钮 SWT.CHECK多选按钮 SWT.RADIO单选按钮 SWT.ARROW箭头按钮 SWT.NONE默认按钮 SWT. ...
- javascript中的style只能取到在HTML中定义的css属性
如果在css中定义的 li{ width:100px; left:100px; top:; position:absolute; font-style:normal; } 这样执行: oli[0].s ...
- discuz 3.x ssrf分析
discuz 3.x版本ssrf漏洞分析 漏洞促发点\souce\module\forum\forum_ajax.php 最后看到了这里 ***$_GET['action']='downremotei ...
- hdu5726-GCD-ST表+二分
先用st表处理出所有l-r的GCD值,然后二分求得这些值一共出现了多少次. #include<bits/stdc++.h> #define inf 0x3f3f3f3f ; using n ...
- 位运算>>和>>>区别
int a=-1; Integer b=0; Integer c=0; System.out.println(Integer.toBinaryString(a)); b=a>>1; c=a ...
- Rebus消息总线
这里主要讲一下我基于Rebus写的一个ABP框架的模块 目录结构 对于Rebus网上的资料很少,其实我对于服务总线也不是很理解 ..个人理解的就是像ABP中的EventBus那样的,但是集成了一些 ...
- c++11 thread的学习
http://www.cnblogs.com/wxquare/p/6736202.html 还没开始 留个链接 使用c++11 thread支持实现 一个生产者消费者模型 下面是一个生产者消费者问题 ...