瀑布流你们都应该熟悉了,如今大部分电商应用中或多或少的都用到瀑布流,它能够吸引用户的眼球,使用户不易产生视觉疲劳,苹果在iOS6中增添了UICollectionView控件,这个控件能够说是UITableView的升级版,经过这个控件咱们就能很简单的作出瀑布流,后面经过本身的封装可让其变成一个小框架,更简单的应用到咱们以后的开发中数组
最近开通了简书欢迎你们关注,我会不按期的分享个人iOS开发经验 点击关注-->Melody_Zhy缓存
框架
- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect { NSArray *arr = [super layoutAttributesForElementsInRect:rect]; for (UICollectionViewLayoutAttributes *attri in arr) { attri.frame = CGRectMake(0, 0, 100, 300); } NSLog(@"%@", arr); return arr; }
这个方法会将collectionView中的全部子控件的布局属性计算一次,计算以后就会被缓存起来,当已经计算过的cell,再次出现时也不会在重复去计算它的尺寸。布局
// 把用来装全部布局属性的数据作清空处理 [self.attrArrM removeAllObjects];
如今定义一个可变数组属性来存储一会本身计算的布局属性中的frame,让上面的方法返回本身定义的布局属性ui
// 用来保存全部布局属性的可变数组 @property (nonatomic, strong) NSMutableArray *attrArrM;
那么咱们在哪一个方法中计算本身定义的布局属性呢?atom
有这么个方法 spa
- (void)prepareLayout { // 要调用父类的prepareLayout [super prepareLayout]; }
在这个方法中能够经过collectionViewFlowLayout的collectionView的numberOfItemInSection这个方法得到一组中的全部cell代理
// 得到一组中的全部cell NSInteger cellCount = [self.collectionView numberOfItemsInSection:0];
经过for循环(循环次数为一组中有多少个cell)建立布局属性,在循环中须要计算每个cell的frame 因此后台要给真实的图片尺寸(若是不给,本身计算的尺寸会形成图片闪)code
同时显示时通常都会等比例缩放。orm
CGFloat contentWidth = self.collectionView.bounds.size.width - self.sectionInset.left - self.sectionInset.right; CGFloat cellW = (contentWidth - (self.columnCount - 1) * self.minimumInteritemSpacing) / self.columnCount;
当要得到cell高的时候须要经过控制器来得到模型图片的高度(高度要和itemW有必定的比例要不图片会过大 height / width * itemW;),所以须要让控制器成为咱们的代理,在自定义CollectionViewFlowLayout.h文件中定义协议以下:
#import <UIKit/UIKit.h> @class ZHYCollectionViewFlowLayout; @protocol ZHYCollectionViewFlowLayoutDelegate <NSObject> @required - (CGFloat)waterFallFlowLayoutWithItemHeight:(ZHYCollectionViewFlowLayout *)flowLayout itemW:(CGFloat)itemW CellIndexPath:(NSIndexPath *)indexPath; @end
@property (weak, nonatomic) id<ZHYCollectionViewFlowLayoutDelegate> delegate
在计算cell高的时候调用代理方法得到cell的高度
CGFloat cellH = [self.delegate waterFallFlowLayoutWithItemHeight:self itemW:cellW CellIndexPath:indexPath];
在计算cellX的时候,若是经过 NSInteger col = i % self.columnCount;获取列号
这样每次都是按顺序排列图片的,那么若是图片的尺寸良莠不齐有的特别短有的又特别长,不巧的是长的都在一列短的又都在一列这样会形成美观性会不好,那么怎么解决这个问题呢,咱们能不能让每一行添加完毕后,下一行在添加的时候将第一个添加在上一行高度最短的那面图片下面呢?答案固然是能够的-_-!
这时候咱们就要定义一个可变字典属性,来存储每一列的高度,用列号来当字典的key
// 用来记录每一列的最的高度 @property (nonatomic, strong) NSMutableDictionary *colDict;
for (NSInteger i = 0; i<有几列; i++) { NSString *str = [NSString stringWithFormat:@"%ld", i]; self.colDict[str] = @(self.sectionInset.top); }
- (NSString *)minCol { __block NSString *min = @"0"; [self.colDict enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) { if ([obj floatValue] < [self.colDict[min] floatValue]) { min = key; } }]; return min; }
NSInteger col = [[self minCol] integerValue];
CGFloat cellX = self.sectionInset.left + (cellW + self.minimumInteritemSpacing) * col;
计算cellY
// 用列号当字典的key NSString *colStr = [NSString stringWithFormat:@"%ld", col]; CGFloat cellY = [self.colDict[colStr] floatValue]; // 累计每一列的高度 self.colDict[colStr] = @(cellY + cellH + self.minimumLineSpacing);
这样咱们就计算完了每个cell的X,Y,W,H,咱们来设置布局属性的frame
// 设置属性的frame attr.frame = CGRectMake(cellX, cellY, cellW, cellH);
// 建立尾部视图的布局属性 // 建立footerview索引 NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:0]; // 必须是额外的layoutAttributesForSupplementaryViewOfKind UICollectionViewLayoutAttributes *footerAttr = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind: UICollectionElementKindSectionFooter withIndexPath:indexPath]; footerAttr.frame = CGRectMake(0, [self.colDict[self.maxCol] floatValue] - self.minimumLineSpacing, self.collectionView.bounds.size.width, 50); [self.attrArrM addObject:footerAttr];
// 用来取出最高那一列的列号 - (NSString *)maxCol { __block NSString *maxCol = @"0"; [self.colDict enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) { if ([obj floatValue] > [self.colDict[maxCol] floatValue]) { maxCol = key; } }]; return maxCol; }
- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect { return self.attrArrM; }
- (CGSize)collectionViewContentSize { return CGSizeMake(0, [self.colDict[self.maxCol] floatValue] + self.footerReferenceSize.height - self.minimumLineSpacing); }
这时基本已经完成了,但若是我想要把这个瀑布流布局作成一个简单的框架就须要在简单的实现些初始化方法
在CollectionViewFlowLayout.h还要定义一个一行有几个cell的属性,当控制器引用这个类以后能够自行设置
@property (assign, nonatomic) NSInteger columnCount;
提供一些初始化方法,使其默认为一行有3个cell, cell间距及行间距为10,内边距为顶部20, footerReferenceSize, headerReferenceSize都为50,50
- (instancetype)init { self = [super init]; if (self) { self.columnCount = 3; self.minimumInteritemSpacing = 10; self.minimumLineSpacing = 10; self.footerReferenceSize = CGSizeMake(50, 50); self.headerReferenceSize = CGSizeMake(50, 50); self.sectionInset = UIEdgeInsetsMake(20, 0, 0, 0); } return self; }
- (instancetype)initWithCoder:(NSCoder *)aDecoder { self = [super initWithCoder:aDecoder]; if (self) { self.columnCount = 3; self.minimumInteritemSpacing = 10; self.minimumLineSpacing = 10; self.footerReferenceSize = CGSizeMake(50, 50); self.headerReferenceSize = CGSizeMake(50, 50); self.sectionInset = UIEdgeInsetsMake(20, 0, 0, 0); } return self; }
这样咱们简单的瀑布流框架就搭建成功了~