瀑布流封装(仿写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大小不固定的,看起来是不是别有一番风味,确实如此. ...
随机推荐
- [转] iOS开发-搜索栏UISearchBar和UISearchController
原文网址: http://www.cnblogs.com/xiaofeixiang/p/4273620.html?utm_source=tuicool iOS中UISearchDisplayContr ...
- 01分数规划初探?!By cellur925
都要\(NOIp\)了为啥我还在看这种玄学玩意..... \(01\)分数规划:这是一个问题模型\(qwq\),一般是在求\[\frac{\sum_{i=1}^{n} a_i*x_i}{\sum_{i ...
- [LOJ 2190] 「SHOI2014」信号增幅仪
[LOJ 2190] 「SHOI2014」信号增幅仪 链接 链接 题解 坐标系直到 \(x\) 轴与椭圆长轴平行 点的坐标变换用旋转公式就可以了 因为是椭圆,所以所有点横坐标除以 \(p\) 然后最小 ...
- Netty(1-2)Discard Client
一.DiscardClientHandler import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelFuture; import ...
- Middleware-请求管道的构成
Middleware-请求管道的构成 在 ASP.NET 中,我们知道,它有一个面向切面的请求管道,有19个主要的事件构成,能够让我们进行灵活的扩展.通常是在 web.config 中通过注册 Htt ...
- python2 学习 数据类型和变量
数据类型和变量 数据类型 整数 Python可以处理任意大小的整数,当然包括负整数,在程序中的表示方法和数学上的写法一模一样,例如:1,100,-8080,0,等等. 计算机由于使用二进制,所以,有时 ...
- AD中添加中文字符丝印的方法:
一 一般中文丝印: 用快捷键L打开层管理,在View options中勾选convert special 选项: 用快捷键P,S文本中输入你要的汉字,选中ture type,在select ture ...
- Codeforces Round #378 (Div. 2) D. Kostya the Sculptor 分组 + 贪心
http://codeforces.com/contest/733/problem/D 给定n个长方体,然后每个长方体都能选择任何一个面,去和其他长方体接在一起,也可以自己一个,要求使得新的长方体的最 ...
- ssis-oracle 数据流任务
[OLE DB 源 1 [16]] 错误: SSIS 错误代码 DTS_E_CANNOTACQUIRECONNECTIONFROMCONNECTIONMANAGER.对连接管理器“F360DB”的 A ...
- 自动生成sql
添加下面这个类 public static class GetAllAttribute<T> where T : class { public static string Names; p ...