collectionView自动布局实现瀑布流
UIcollectionView是我们在做iOS高级视图中经常用到的控件,他的特点就是非常灵活。只要有算法支撑,他无所不能。但是同时我们发现自带的原声的布局往往满足不了我们对于界面瀑布流的需求。所以我们玩玩自定义布局,使用算法来满足我们界面需求
1.自动布局的.h文件
//瀑布流布局 #import <UIKit/UIKit.h> @class YZFlowtLayout; @protocol YZXWaterflowLayoutDelegate <NSObject> - (CGFloat)waterflowLayout:(YZFlowtLayout *)waterflowLayout heightForWidth:(CGFloat)width atIndexPath:(NSIndexPath *)indexPath; @end @interface YZFlowtLayout : UICollectionViewLayout //周边距离 @property (nonatomic, assign) UIEdgeInsets sectionInset; /** 每一列之间的间距 */ @property (nonatomic, assign) CGFloat columnMargin; /** 每一行之间的间距 */ @property (nonatomic, assign) CGFloat rowMargin; /** 显示多少列 */ @property (nonatomic, assign) int columnsCount; @property (nonatomic, weak) id<YZXWaterflowLayoutDelegate>delegate; @end
1.1上面是我们从布局属性当中抽取出来的几个属性对象,那么问题来了,为何要将他们抽取出来呢。原因在于我们封装要考虑需求的变动性。能够适用多种情景才是我们需要的。
1.2如代码属性所示,我们将其布局属性中的周边距离属性,cell行间距属性,cell列间距属性和cell的列个数属性抽取出来,那么我们就可以变动这些属性满足不同的需求。我们仔细观察这几种抽取出的属性,我们会发现这几个属性设置完全是我们一般垂直瀑布流所需要的,也就是说我们封装的这个布局类能够满足所有一般竖直瀑布流
2自动布局.m文件
#import "YZFlowtLayout.h" @interface YZFlowtLayout() /** 这个字典用来存储每一列最大的Y值(每一列的高度) */ @property (nonatomic, strong) NSMutableDictionary *maxYDict; /** 存放所有的布局属性 */ @property (nonatomic, strong) NSMutableArray *attrsArray; @end @implementation YZFlowtLayout #pragma mark - 懒加载 - (NSMutableDictionary *)maxYDict { if (!_maxYDict) { self.maxYDict = [[NSMutableDictionary alloc] init]; } return _maxYDict; } - (NSMutableArray *)attrsArray { if (!_attrsArray) { self.attrsArray = [[NSMutableArray alloc] init]; } return _attrsArray; } - (id)init { if(self = [super init]){ //默认 self.columnMargin = 10; self.rowMargin = 10; self.sectionInset = UIEdgeInsetsMake(10, 10, 10, 10); self.columnsCount = 2; } return self; } //布局跟新 - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds { return YES; } //每次布局之前的准备 - (void)prepareLayout { [super prepareLayout]; // 清空最大的Y值 for (int i= 0; i < self.columnsCount; i++) { NSString *column = [NSString stringWithFormat:@"%d", i]; self.maxYDict[column] = @(self.sectionInset.top); } [self.attrsArray removeAllObjects]; // 计算每组有多少个cell NSInteger count = [self.collectionView numberOfItemsInSection:0]; // 计算所有cell的属性 for (int i = 0; i < count; i ++) { UICollectionViewLayoutAttributes * attrs = [self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:i inSection:0]]; [self.attrsArray addObject:attrs]; } } //返回所有的尺寸 - (CGSize)collectionViewContentSize { __block NSString * maxColum = @"0"; [self.maxYDict enumerateKeysAndObjectsUsingBlock:^(NSString *column, NSNumber * maxY, BOOL * _Nonnull stop) { if ([maxY floatValue] > [self.maxYDict[maxColum] floatValue]) { maxColum = column; } }]; return CGSizeMake(0, [self.maxYDict[maxColum] floatValue] + self.sectionInset.bottom); } /** * 返回indexPath这个位置Item的布局属性 */ - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath { // 假设最短的那一列的第0列 __block NSString *minColumn = @"0"; // 找出最短的那一列 [self.maxYDict enumerateKeysAndObjectsUsingBlock:^(NSString *column, NSNumber *maxY, BOOL *stop) { if ([maxY floatValue] < [self.maxYDict[minColumn] floatValue]) { minColumn = column; } }]; // 计算尺寸 CGFloat width = (self.collectionView.frame.size.width - self.sectionInset.left - self.sectionInset.right - (self.columnsCount - 1) * self.columnMargin)/self.columnsCount; CGFloat height = [self.delegate waterflowLayout:self heightForWidth:width atIndexPath:indexPath]; // 计算位置 CGFloat x = self.sectionInset.left + (width + self.columnMargin) * [minColumn intValue]; CGFloat y = [self.maxYDict[minColumn] floatValue] + self.rowMargin; // 更新这一列的最大Y值 self.maxYDict[minColumn] = @(y + height); // 创建属性 UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; attrs.frame = CGRectMake(x, y, width, height); return attrs; } /** * 布局属性集合数组 */ - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { return self.attrsArray; } @end
2.1我们布局无非是想得到显示界面中的每一个cell的位置