1 #import <UIKit/UIKit.h> 2 3 typedef enum { 4 iCocosWaterflowViewMarginTypeTop, 5 iCocosWaterflowViewMarginTypeBottom, 6 iCocosWaterflowViewMarginTypeLeft, 7 iCocosWaterflowViewMarginTypeRight, 8 iCocosWaterflowViewMarginTypeColumn, // 每一列 9 iCocosWaterflowViewMarginTypeRow, // 每一行 10 } iCocosWaterflowViewMarginType; 11 12 @class iCocosWaterflowView, iCocosWaterflowViewCell; 13 14 /** 15 * 数据源方法 16 */ 17 @protocol iCocosWaterflowViewDataSource <NSObject> 18 @required 19 /** 20 * 一共有多少个数据 21 */ 22 - (NSUInteger)numberOfCellsInWaterflowView:(iCocosWaterflowView *)waterflowView; 23 /** 24 * 返回index位置对应的cell 25 */ 26 - (iCocosWaterflowViewCell *)waterflowView:(iCocosWaterflowView *)waterflowView cellAtIndex:(NSUInteger)index; 27 28 @optional 29 /** 30 * 一共有多少列 31 */ 32 - (NSUInteger)numberOfColumnsInWaterflowView:(iCocosWaterflowView *)waterflowView; 33 @end 34 35 /** 36 * 代理方法 37 */ 38 @protocol iCocosWaterflowViewDelegate <UIScrollViewDelegate> 39 @optional 40 /** 41 * 第index位置cell对应的高度 42 */ 43 - (CGFloat)waterflowView:(iCocosWaterflowView *)waterflowView heightAtIndex:(NSUInteger)index; 44 /** 45 * 选中第index位置的cell 46 */ 47 - (void)waterflowView:(iCocosWaterflowView *)waterflowView didSelectAtIndex:(NSUInteger)index; 48 /** 49 * 返回间距 50 */ 51 - (CGFloat)waterflowView:(iCocosWaterflowView *)waterflowView marginForType:(iCocosWaterflowViewMarginType)type; 52 @end 53 54 /** 55 * 瀑布流控件 56 */ 57 @interface iCocosWaterflowView : UIScrollView 58 /** 59 * 数据源 60 */ 61 @property (nonatomic, weak) id<iCocosWaterflowViewDataSource> dataSource; 62 /** 63 * 代理 64 */ 65 @property (nonatomic, weak) id<iCocosWaterflowViewDelegate> delegate; 66 67 /** 68 * 刷新数据(只要调用这个方法,会重新向数据源和代理发送请求,请求数据) 69 */ 70 - (void)reloadData; 71 72 /** 73 * cell的宽度 74 */ 75 - (CGFloat)cellWidth; 76 77 /** 78 * 根据标识去缓存池查找可循环利用的cell 79 */ 80 - (id)dequeueReusableCellWithIdentifier:(NSString *)identifier; 81 @end
View.m文件
1 #import "iCocosWaterflowView.h" 2 #import "iCocosWaterflowViewCell.h" 3 4 #define iCocosWaterflowViewDefaultCellH 70 5 #define iCocosWaterflowViewDefaultMargin 8 6 #define iCocosWaterflowViewDefaultNumberOfColumns 3 7 8 @interface iCocosWaterflowView() 9 /** 10 * 所有cell的frame数据 11 */ 12 @property (nonatomic, strong) NSMutableArray *cellFrames; 13 /** 14 * 正在展示的cell 15 */ 16 @property (nonatomic, strong) NSMutableDictionary *displayingCells; 17 /** 18 * 缓存池(用Set,存放离开屏幕的cell) 19 */ 20 @property (nonatomic, strong) NSMutableSet *reusableCells; 21 @end 22 23 @implementation iCocosWaterflowView 24 25 #pragma mark - 初始化 26 - (NSMutableArray *)cellFrames 27 { 28 if (_cellFrames == nil) { 29 self.cellFrames = [NSMutableArray array]; 30 } 31 return _cellFrames; 32 } 33 34 - (NSMutableDictionary *)displayingCells 35 { 36 if (_displayingCells == nil) { 37 self.displayingCells = [NSMutableDictionary dictionary]; 38 } 39 return _displayingCells; 40 } 41 42 - (NSMutableSet *)reusableCells 43 { 44 if (_reusableCells == nil) { 45 self.reusableCells = [NSMutableSet set]; 46 } 47 return _reusableCells; 48 } 49 50 - (id)initWithFrame:(CGRect)frame 51 { 52 self = [super initWithFrame:frame]; 53 if (self) { 54 55 } 56 return self; 57 } 58 59 - (void)willMoveToSuperview:(UIView *)newSuperview 60 { 61 [self reloadData]; 62 } 63 64 #pragma mark - 公共接口 65 /** 66 * cell的宽度 67 */ 68 - (CGFloat)cellWidth 69 { 70 // 总列数 71 int numberOfColumns = [self numberOfColumns]; 72 CGFloat leftM = [self marginForType:iCocosWaterflowViewMarginTypeLeft]; 73 CGFloat rightM = [self marginForType:iCocosWaterflowViewMarginTypeRight]; 74 CGFloat columnM = [self marginForType:iCocosWaterflowViewMarginTypeColumn]; 75 return (self.bounds.size.width - leftM - rightM - (numberOfColumns - 1) * columnM) / numberOfColumns; 76 } 77 78 /** 79 * 刷新数据 80 */ 81 - (void)reloadData 82 { 83 // 清空之前的所有数据 84 // 移除正在正在显示cell 85 [self.displayingCells.allValues makeObjectsPerformSelector:@selector(removeFromSuperview)]; 86 [self.displayingCells removeAllObjects]; 87 [self.cellFrames removeAllObjects]; 88 [self.reusableCells removeAllObjects]; 89 90 // cell的总数 91 int numberOfCells = [self.dataSource numberOfCellsInWaterflowView:self]; 92 93 // 总列数 94 int numberOfColumns = [self numberOfColumns]; 95 96 // 间距 97 CGFloat topM = [self marginForType:iCocosWaterflowViewMarginTypeTop]; 98 CGFloat bottomM = [self marginForType:iCocosWaterflowViewMarginTypeBottom]; 99 CGFloat leftM = [self marginForType:iCocosWaterflowViewMarginTypeLeft]; 100 CGFloat columnM = [self marginForType:iCocosWaterflowViewMarginTypeColumn]; 101 CGFloat rowM = [self marginForType:iCocosWaterflowViewMarginTypeRow]; 102 103 // cell的宽度 104 CGFloat cellW = [self cellWidth]; 105 106 // 用一个C语言数组存放所有列的最大Y值 107 CGFloat maxYOfColumns[numberOfColumns]; 108 for (int i = 0; i<numberOfColumns; i++) { 109 maxYOfColumns[i] = 0.0; 110 } 111 112 // 计算所有cell的frame 113 for (int i = 0; i<numberOfCells; i++) { 114 // cell处在第几列(最短的一列) 115 NSUInteger cellColumn = 0; 116 // cell所处那列的最大Y值(最短那一列的最大Y值) 117 CGFloat maxYOfCellColumn = maxYOfColumns[cellColumn]; 118 // 求出最短的一列 119 for (int j = 1; j<numberOfColumns; j++) { 120 if (maxYOfColumns[j] < maxYOfCellColumn) { 121 cellColumn = j; 122 maxYOfCellColumn = maxYOfColumns[j]; 123 } 124 } 125 126 // 询问代理i位置的高度 127 CGFloat cellH = [self heightAtIndex:i]; 128 129 // cell的位置 130 CGFloat cellX = leftM + cellColumn * (cellW + columnM); 131 CGFloat cellY = 0; 132 if (maxYOfCellColumn == 0.0) { // 首行 133 cellY = topM; 134 } else { 135 cellY = maxYOfCellColumn + rowM; 136 } 137 138 // 添加frame到数组中 139 CGRect cellFrame = CGRectMake(cellX, cellY, cellW, cellH); 140 [self.cellFrames addObject:[NSValue valueWithCGRect:cellFrame]]; 141 142 // 更新最短那一列的最大Y值 143 maxYOfColumns[cellColumn] = CGRectGetMaxY(cellFrame); 144 } 145 146 // 设置contentSize 147 CGFloat contentH = maxYOfColumns[0]; 148 for (int j = 1; j<numberOfColumns; j++) { 149 if (maxYOfColumns[j] > contentH) { 150 contentH = maxYOfColumns[j]; 151 } 152 } 153 contentH += bottomM; 154 self.contentSize = CGSizeMake(0, contentH); 155 } 156 157 /** 158 * 当UIScrollView滚动的时候也会调用这个方法 159 */ 160 - (void)layoutSubviews 161 { 162 [super layoutSubviews]; 163 164 // 向数据源索要对应位置的cell 165 NSUInteger numberOfCells = self.cellFrames.count; 166 for (int i = 0; i<numberOfCells; i++) { 167 // 取出i位置的frame 168 CGRect cellFrame = [self.cellFrames[i] CGRectValue]; 169 170 // 优先从字典中取出i位置的cell 171 iCocosWaterflowViewCell *cell = self.displayingCells[@(i)]; 172 173 // 判断i位置对应的frame在不在屏幕上(能否看见) 174 if ([self isInScreen:cellFrame]) { // 在屏幕上 175 if (cell == nil) { 176 cell = [self.dataSource waterflowView:self cellAtIndex:i]; 177 cell.frame = cellFrame; 178 [self addSubview:cell]; 179 180 // 存放到字典中 181 self.displayingCells[@(i)] = cell; 182 } 183 } else { // 不在屏幕上 184 if (cell) { 185 // 从scrollView和字典中移除 186 [cell removeFromSuperview]; 187 [self.displayingCells removeObjectForKey:@(i)]; 188 189 // 存放进缓存池 190 [self.reusableCells addObject:cell]; 191 } 192 } 193 } 194 } 195 196 - (id)dequeueReusableCellWithIdentifier:(NSString *)identifier 197 { 198 __block iCocosWaterflowViewCell *reusableCell = nil; 199 200 [self.reusableCells enumerateObjectsUsingBlock:^(iCocosWaterflowViewCell *cell, BOOL *stop) { 201 if ([cell.identifier isEqualToString:identifier]) { 202 reusableCell = cell; 203 *stop = YES; 204 } 205 }]; 206 207 if (reusableCell) { // 从缓存池中移除 208 [self.reusableCells removeObject:reusableCell]; 209 } 210 return reusableCell; 211 } 212 213 #pragma mark - 私有方法 214 /** 215 * 判断一个frame有无显示在屏幕上 216 */ 217 - (BOOL)isInScreen:(CGRect)frame 218 { 219 return (CGRectGetMaxY(frame) > self.contentOffset.y) && 220 (CGRectGetMinY(frame) < self.contentOffset.y + self.bounds.size.height); 221 } 222 223 /** 224 * 间距 225 */ 226 - (CGFloat)marginForType:(iCocosWaterflowViewMarginType)type 227 { 228 if ([self.delegate respondsToSelector:@selector(waterflowView:marginForType:)]) { 229 return [self.delegate waterflowView:self marginForType:type]; 230 } else { 231 return iCocosWaterflowViewDefaultMargin; 232 } 233 } 234 /** 235 * 总列数 236 */ 237 - (NSUInteger)numberOfColumns 238 { 239 if ([self.dataSource respondsToSelector:@selector(numberOfColumnsInWaterflowView:)]) { 240 return [self.dataSource numberOfColumnsInWaterflowView:self]; 241 } else { 242 return iCocosWaterflowViewDefaultNumberOfColumns; 243 } 244 } 245 /** 246 * index位置对应的高度 247 */ 248 - (CGFloat)heightAtIndex:(NSUInteger)index 249 { 250 if ([self.delegate respondsToSelector:@selector(waterflowView:heightAtIndex:)]) { 251 return [self.delegate waterflowView:self heightAtIndex:index]; 252 } else { 253 return iCocosWaterflowViewDefaultCellH; 254 } 255 } 256 257 #pragma mark - 事件处理 258 - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event 259 { 260 if (![self.delegate respondsToSelector:@selector(waterflowView:didSelectAtIndex:)]) return; 261 262 // 获得触摸点 263 UITouch *touch = [touches anyObject]; 264 // CGPoint point = [touch locationInView:touch.view]; 265 CGPoint point = [touch locationInView:self]; 266 267 __block NSNumber *selectIndex = nil; 268 [self.displayingCells enumerateKeysAndObjectsUsingBlock:^(id key, iCocosWaterflowViewCell *cell, BOOL *stop) { 269 if (CGRectContainsPoint(cell.frame, point)) { 270 selectIndex = key; 271 *stop = YES; 272 } 273 }]; 274 275 if (selectIndex) { 276 [self.delegate waterflowView:self didSelectAtIndex:selectIndex.unsignedIntegerValue]; 277 } 278 }
Cell.h文件
1 #import <UIKit/UIKit.h> 2 3 @interface iCocosWaterflowViewCell : UIView 4 @property (nonatomic, copy) NSString *identifier; 5 @end
使用方法:
1:新建一个Cell模型
1 #import <Foundation/Foundation.h> 2 3 @interface iCocosShop : NSObject 4 @property (nonatomic, assign) CGFloat w; 5 @property (nonatomic, assign) CGFloat h; 6 @property (nonatomic, copy) NSString *img; 7 @property (nonatomic, copy) NSString *price; 8 @end
2:定义一个集成自上面的Cell的Cell
1 #import "iCocosWaterflowViewCell.h" 2 @class iCocosWaterflowView, iCocosShop; 3 4 @interface iCocosShopCell : iCocosWaterflowViewCell 5 + (instancetype)cellWithWaterflowView:(iCocosWaterflowView *)waterflowView; 6 7 @property (nonatomic, strong) iCocosShop *shop; 8 @end
实现这个Cell:
1 #import "iCocosShopCell.h" 2 #import "iCocosWaterflowView.h" 3 #import "UIImageView+WebCache.h" 4 #import "iCocosShop.h" 5 6 @interface iCocosShopCell() 7 @property (weak, nonatomic) UIImageView *imageView; 8 @property (weak, nonatomic) UILabel *priceLabel; 9 @end 10 11 @implementation iCocosShopCell 12 13 14 + (instancetype)cellWithWaterflowView:(iCocosWaterflowView *)waterflowView 15 { 16 static NSString *ID = @"SHOP"; 17 iCocosShopCell *cell = [waterflowView dequeueReusableCellWithIdentifier:ID]; 18 if (cell == nil) { 19 cell = [[iCocosShopCell alloc] init]; 20 cell.identifier = ID; 21 } 22 return cell; 23 } 24 25 - (id)initWithFrame:(CGRect)frame 26 { 27 self = [super initWithFrame:frame]; 28 if (self) { 29 30 UIImageView *imageView = [[UIImageView alloc] init]; 31 [self addSubview:imageView]; 32 self.imageView = imageView; 33 34 UILabel *priceLabel = [[UILabel alloc] init]; 35 priceLabel.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.3]; 36 priceLabel.textAlignment = NSTextAlignmentCenter; 37 priceLabel.textColor = [UIColor whiteColor]; 38 [self addSubview:priceLabel]; 39 self.priceLabel = priceLabel; 40 } 41 return self; 42 } 43 44 - (void)setShop:(iCocosShop *)shop 45 { 46 _shop = shop; 47 48 self.priceLabel.text = shop.price; 49 [self.imageView sd_setImageWithURL:[NSURL URLWithString:shop.img] placeholderImage:[UIImage imageNamed:@"loading"]]; 50 } 51 52 - (void)layoutSubviews 53 { 54 [super layoutSubviews]; 55 56 self.imageView.frame = self.bounds; 57 58 CGFloat priceX = 0; 59 CGFloat priceH = 25; 60 CGFloat priceY = self.bounds.size.height - priceH; 61 CGFloat priceW = self.bounds.size.width; 62 self.priceLabel.frame = CGRectMake(priceX, priceY, priceW, priceH); 63 }
3:在控制器中直接使用:
1 #import "iCocosShopsViewController.h" 2 #import "iCocosShopCell.h" 3 #import "iCocosWaterflowView.h" 4 #import "iCocosShop.h" 5 #import "MJExtension.h" 6 #import "MJRefresh.h" 7 8 @interface iCocosShopsViewController ()<iCocosWaterflowViewDataSource, iCocosWaterflowViewDelegate> 9 @property (nonatomic, strong) NSMutableArray *shops; 10 @property (nonatomic, weak) iCocosWaterflowView *waterflowView; 11 @end 12 13 @implementation iCocosShopsViewController 14 15 - (NSMutableArray *)shops 16 { 17 if (_shops == nil) { 18 self.shops = [NSMutableArray array]; 19 } 20 return _shops; 21 } 22 23 - (void)viewDidLoad 24 { 25 [super viewDidLoad]; 26 27 // 0.初始化数据 28 NSArray *newShops = [iCocosShop objectArrayWithFilename:@"2.plist"]; 29 [self.shops addObjectsFromArray:newShops]; 30 31 // 1.瀑布流控件 32 iCocosWaterflowView *waterflowView = [[iCocosWaterflowView alloc] init]; 33 waterflowView.backgroundColor = [UIColor redColor]; 34 // 跟随着父控件的尺寸而自动伸缩 35 waterflowView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; 36 waterflowView.frame = self.view.bounds; 37 waterflowView.dataSource = self; 38 waterflowView.delegate = self; 39 [self.view addSubview:waterflowView]; 40 self.waterflowView = waterflowView; 41 42 // 2.继承刷新控件 43 // [waterflowView addFooterWithCallback:^{ 44 // NSLog(@"进入上拉加载状态"); 45 // }]; 46 47 // [waterflowView addHeaderWithCallback:^{ 48 // NSLog(@"进入下拉加载状态"); 49 // }]; 50 51 [waterflowView addHeaderWithTarget:self action:@selector(loadNewShops)]; 52 [waterflowView addFooterWithTarget:self action:@selector(loadMoreShops)]; 53 } 54 55 - (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation 56 { 57 // NSLog(@"屏幕旋转完毕"); 58 [self.waterflowView reloadData]; 59 } 60 61 - (void)loadNewShops 62 { 63 static dispatch_once_t onceToken; 64 dispatch_once(&onceToken, ^{ 65 // 加载1.plist 66 NSArray *newShops = [iCocosShop objectArrayWithFilename:@"1.plist"]; 67 [self.shops insertObjects:newShops atIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, newShops.count)]]; 68 }); 69 70 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 71 // 刷新瀑布流控件 72 [self.waterflowView reloadData]; 73 74 // 停止刷新 75 [self.waterflowView headerEndRefreshing]; 76 }); 77 } 78 79 - (void)loadMoreShops 80 { 81 static dispatch_once_t onceToken; 82 dispatch_once(&onceToken, ^{ 83 // 加载3.plist 84 NSArray *newShops = [iCocosShop objectArrayWithFilename:@"3.plist"]; 85 [self.shops addObjectsFromArray:newShops]; 86 }); 87 88 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 89 90 // 刷新瀑布流控件 91 [self.waterflowView reloadData]; 92 93 // 停止刷新 94 [self.waterflowView footerEndRefreshing]; 95 }); 96 } 97 98 #pragma mark - 数据源方法 99 - (NSUInteger)numberOfCellsInWaterflowView:(iCocosWaterflowView *)waterflowView 100 { 101 return self.shops.count; 102 } 103 104 - (iCocosWaterflowViewCell *)waterflowView:(iCocosWaterflowView *)waterflowView cellAtIndex:(NSUInteger)index 105 { 106 iCocosShopCell *cell = [iCocosShopCell cellWithWaterflowView:waterflowView]; 107 108 cell.shop = self.shops[index]; 109 110 return cell; 111 } 112 113 - (NSUInteger)numberOfColumnsInWaterflowView:(iCocosWaterflowView *)waterflowView 114 { 115 if (UIInterfaceOrientationIsPortrait(self.interfaceOrientation)) { 116 // 竖屏 117 return 3; 118 } else { 119 return 5; 120 } 121 } 122 123 #pragma mark - 代理方法 124 - (CGFloat)waterflowView:(iCocosWaterflowView *)waterflowView heightAtIndex:(NSUInteger)index 125 { 126 iCocosShop *shop = self.shops[index]; 127 // 根据cell的宽度 和 图片的宽高比 算出 cell的高度 128 return waterflowView.cellWidth * shop.h / shop.w; 129 }
注:这里还需要引入第三方库和相应的工具类