UICollectionViewLayout高度自定义瀑布流
知识重在分享, 只有分享才能逐步提高创新力!
实现简单的UICollectionView必须实现UICollectionViewDataSource协议, 知道几组, 每组几个item, 和UITableView不同的是UICollectionView每个item的具体布局信息需要UICollectionViewFlowLayout类来提供, 如果没有实现UICollectionViewDelegateFlowLayout协议, 那么每个item的布局约束可以直接使用
@property (nonatomic) CGFloat minimumLineSpacing; //行间距
@property (nonatomic) CGFloat minimumInteritemSpacing; //item间距
@property (nonatomic) CGSize itemSize; //item大小
@property (nonatomic) CGSize headerReferenceSize; //组头大小
@property (nonatomic) CGSize footerReferenceSize; //组尾大小
@property (nonatomic) UIEdgeInsets sectionInset; //内容内间距
这些属性来设置
如果遵守了UICollectionViewDelegateFlowLayout协议, 那么每个item的布局约束需要实现代理方法来设置, 具体如下:
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath; //每个item大小
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section; //内容内间距
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section; //行间距
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section; //item间距
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section; //组头大小
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section; //组尾大小
对于遵守UICollectionViewDelegateFlowLayout协议的UICollectionView, 比如设置
UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc]init];
flowLayout.minimumLineSpacing = 10;
flowLayout.minimumInteritemSpacing = 10;
flowLayout.sectionInset = UIEdgeInsetsMake(20, 20, 20, 20);
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath{
CGFloat width = self.view.bounds.size.width;
return CGSizeMake((width-40-20)/3, rand()%40+50);
}
那么效果会是这样的:

这样的效果有时候并不能满足我们的业务需求, 我们的业务有时候是需要垂直布局, 可以设置几列, 每列之间的item等间距布局, 这时候系统默认的行为已经不能满足我们的需要
下面看看如何解决这种业务需求的布局要求:
对于UICollectionView的item的不规则布局以及带动画布局, 都需要自定义UICollectionViewLayout这个类是专门来设置UICollectionView子视图布局的类,其中比较常用的方法有以下几个:
- (void)prepareLayout; //每次UICollectionView添加到父视图的时候, 都会加载该方法, 该方法只会调用一次, 为此可以在该方法中, 初始化一些需要的信息
- (nullable NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect; //返回给定矩形中所有视图的数组布局属性实例
- (nullable UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath; //每个item的布局属性在此方法中设计
- (nullable UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath; //每个组头组尾的布局属性在此方法中设计
- (nullable UICollectionViewLayoutAttributes *)layoutAttributesForDecorationViewOfKind:(NSString*)elementKind atIndexPath:(NSIndexPath *)indexPath; //设计UICollectionView背景视图的布局属性在此方法中进行
UICollectionViewLayoutAttributes是很重要的一个类, 它封装了UICollectionView的cell, 组头组尾, DecorationView(背景视图)的布局属性, 包含属性列表如下:
@property (nonatomic) CGRect frame; //UICollectionView每个子视图的位置,大小
@property (nonatomic) CGPoint center; // UICollectionView每个子视图在父视图的中心点坐标
@property (nonatomic) CGSize size; //UICollectionView每个子视图的大小
@property (nonatomic) CATransform3D transform3D; //可以使用该属性, 设置每个子视图的3D动画效果
@property (nonatomic) CGRect bounds API_AVAILABLE(ios(7.0));
@property (nonatomic) CGAffineTransform transform API_AVAILABLE(ios(7.0));
@property (nonatomic) CGFloat alpha;
@property (nonatomic) NSInteger zIndex; // default is 0 用于3D动画开发上面, 表示空间的深度
@property (nonatomic, getter=isHidden) BOOL hidden; // As an optimization, UICollectionView might not create a view for items whose hidden attribute is YES
@property (nonatomic, strong) NSIndexPath *indexPath; //每个子视图所在的组和几个item的标示
为此为了满足产品的需求, 我们可以自定义一个了继承UICollectionViewLayout, 然后给自定义类添加几个协议方法和属性, 例如:
@protocol WaterfallFlowLayoutDelegate <NSObject>
@required
//根据每个item的宽计算高
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(WaterfallFlowLayout*)collectionViewLayout heightForWidth:(CGFloat)width atIndexPath:(NSIndexPath*)indexPath;
@optional
//当然你也可以加上几个代理方法表示行间距, item间距, 内容内间距等, 这样也可以灵活设置UICollectionViewFlowLayout的每个布局属性
//设置组头的大小
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(WaterfallFlowLayout*)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section;
//设置组尾的大小
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(WaterfallFlowLayout*)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section;
@end
@interface WaterfallFlowLayout : UICollectionViewLayout
@property(nonatomic,assign)UIEdgeInsets sectionInset; //sectionInset
@property(nonatomic,assign)CGFloat lineSpacing; //line space
@property(nonatomic,assign)CGFloat itemSpacing; //item space
@property(nonatomic,assign)CGFloat colCount; //column count
@property(nonatomic,weak)id<WaterfallFlowLayoutDelegate> delegate;
@end
最后就可以在- (void)prepareLayout;方法中初始化一些布局时候需要用到的信息
在- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath;方法中设置每个item的UICollectionViewLayoutAttributes信息
在- (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath;方法中设置组头组尾的UICollectionViewLayoutAttributes信息
在- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect;方法中返回所有计算好的包含每个子视图的UICollectionViewLayoutAttributes的数组
在- (CGSize)collectionViewContentSize;方法中根据最大列的高度, 设置可滚动区域的大小
最终实现效果如下:

具体代码如下, 不容易理解的地方已经加上了详细的解释: