瀑布流封装(仿写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大小不固定的,看起来是不是别有一番风味,确实如此. ...
随机推荐
- 剑指Offer的学习笔记(C#篇)-- 从上往下打印二叉树
题目描述 从上往下打印出二叉树的每个节点,同层节点从左至右打印. 一 . 题目解析 了解过二叉树就应该知道,二叉树存在三种遍历方法:前序遍历(根→左→右).中序遍历(左→根→右).后续遍历(左→右→根 ...
- 使用Git版本控制工具管理GitHub
使用Git版本控制工具管理GitHu Git是一个分步式的管理系统:只要上传操作得当,所有的都可以相当于是中央服务器,成员代码共享,A写的代码B也有,一般把一个人当做主机,其他人通过该主机拼装代码 ...
- wcf双工通信
一直以为感觉双工没弄懂,着实觉得很惆怅,在网上了解下双工的一些特点,直接上代码,以便以后项目中用的着: service层: 定义一个IDuplexHello服务接口 [ServiceContract( ...
- Hive进阶_Hive的客户端操作
启动远程客户端 # hive --service hiveserver2获取连接-〉创建运行环境-〉执行HQL-〉处理结果-〉释放资源 工具类 package demo.utils; import j ...
- 微服务的.NET Core示例框架
eShopOnContainers 是一个基于微服务的.NET Core示例框架 https://www.cnblogs.com/fengqingyangNo1/p/9438428.html 找到一个 ...
- python之三级菜单
python之三级菜单 要求: 1. 运行程序输出第一级菜单 2. 选择一级菜单某项,输出二级菜单,同理输出三级菜单 3. 菜单数据保存在文件中 4. 让用户选择是否要退出 5. 有返回上一级菜单的功 ...
- 找不到 main 方法, 请将 main 方法定义为: public static void main(String[] args)
https://blog.csdn.net/liu1340308350/article/details/80746671
- JSONModel 简单例子
// ProductModel.h // JSONModel // // Created by 张国锋 on 15/7/20. // Copyright (c) 2015年 张国锋. All righ ...
- Java获取服务器系统默认编码格式
大佬教的,做个笔记方法一(推荐):新建一个jsp页面在webapp下然后添加 <% out.print(System.getProperties().getProperty("file ...
- css3响应式图片
响应式图片指用户代理根据输出设备的分辨率不同加载不同类型的图片,不会造成带宽的浪费. 同时,在改变输出设备类型或分辨率时,能及时加载对应类型的图片. 常用的实现方式: 1.用srcset和size ...