一款强大的iOS瀑布流布局LBWaterFallLayout

效果图

image

实现思路

UICollectionView的精髓就是UICollectionViewLayout。UICollectionViewLayout决定了UICollectionView是如何显示在界面上的。因此我们需要自定义一个UICollectionViewLayout 的子类,在子类里面重写生成布局的方法,创建我们自己需要的布局

实现原理

重写- (void)prepareLayout进行提前创建布局

重写- (CGSize)collectionViewContentSize返回内容的大小

重写 - (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect方法返回rect中所有元素的布局属性,返回的是一个数组

重写 - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath方法返回对应的indexPath的位置的cell的布局属性。

重写 - (nullable UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath;方法返回对应indexPath的位置的追加视图的布局属性,如果没有就不用重载

重写 - (nullable UICollectionViewLayoutAttributes )layoutAttributesForDecorationViewOfKind:(NSString)elementKind atIndexPath:(NSIndexPath *)indexPath;方法返回对应indexPath的位置的装饰视图的布局属性,如果没有也不需要重载

重写 - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds;当边界发生改变时,是否应该刷新。

注意:其中

  • (void)prepareLayout

  • (CGSize)collectionViewContentSize

  • (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect

这三个方法是必须要重写的,后面的方法可以选择性重写,因为最终collectionView 还是根据 - (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect 方法来进行展示布局的,

而重写 prepareLayout是因为创建布局的时机 需要提前到 prepareLayout 里面。

而collectionViewContentSize 是返回内容宽和高的,也必须重写

核心计算逻辑

每次都获取到总高度最低的一列cell,在该列添加新的布局

实现代码

协议文件

//
//  LBWaterFallLayoutProtocol.h
//  Pods
//
//  Created by Apple on 2021/9/24.
//

#ifndef LBWaterFallLayoutProtocol_h
#define LBWaterFallLayoutProtocol_h

@protocol LBWaterFallLayoutDelegate<NSObject>

@optional

/// 每个区多少列
- (NSInteger)collectionView:(UICollectionView *)collectionView customLayout:(UICollectionViewLayout *)collectionViewLayout columnNumberAtSection:(NSInteger )section;

/// cell size
- (CGSize)collectionView:(UICollectionView *)collectionView customLayout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath;

/// header size
- (CGSize)collectionView:(UICollectionView *)collectionView customLayout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section;

/// footer size
- (CGSize)collectionView:(UICollectionView *)collectionView customLayout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section;

/// 每个区的边距
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView customLayout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section;

/// 每个区内部的垂直距离
- (CGFloat)collectionView:(UICollectionView *)collectionView customLayout:(UICollectionViewLayout *)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section;

/// 每个区内部的水平距离
- (CGFloat)collectionView:(UICollectionView *)collectionView customLayout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section;

///使用配置的contentSize,和  customContentSize方法配合使用
/// 这时候使用代理方法配置的contentSize
- (BOOL)useCustomContentSize;

///  配置的contentSize
- (CGSize)customContentSize;
/// 装饰视图的类名数组
- (NSArray <Class>*)decorationViewClasses;
/// 根据indexPath获取装饰视图的类
- (Class)decorationViewClassOfIndexPath:(NSIndexPath *)indexPath;

@end

@protocol LBWaterFallLayoutProtocol<NSObject,
LBWaterFallLayoutDelegate>

@property (nonatomic, weak) id<LBWaterFallLayoutDelegate> lb_layoutDelegate;

@end


#endif /* LBWaterFallLayoutProtocol_h */

.h 文件

//
//  LBWaterFallLayout.h
//  LBWaterFallLayout
//
//  Created by Apple on 2021/9/25.
//

#import <UIKit/UIKit.h>
#import "LBWaterFallLayoutProtocol.h"

NS_ASSUME_NONNULL_BEGIN

@interface LBWaterFallLayout : UICollectionViewFlowLayout<LBWaterFallLayoutProtocol>

@property (nonatomic, assign) CGFloat minimumLineSpacing; // default 0.0
@property (nonatomic, assign) CGFloat minimumInteritemSpacing; // default 0.0
@property (nonatomic, assign) BOOL sectionHeadersPinToVisibleBounds; // default NO
@property (nonatomic, assign) CGFloat contentOffsetY;

/// 未悬停的header的布局属性
- (UICollectionViewLayoutAttributes *)originLayoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath;
/// 适用于每一列的cell宽度都相等
- (CGFloat)itemWidthOfIndexPath:(NSIndexPath *)indexPath;

@end

NS_ASSUME_NONNULL_END

.m 文件

//
//  LBWaterFallLayout.m
//  LBWaterFallLayout
//
//  Created by Apple on 2021/9/25.
//

#import "LBWaterFallLayout.h"

@interface LBCollectionLayoutSectionModel : NSObject
@property (nonatomic, strong, nullable) UICollectionViewLayoutAttributes *headerLayoutAttributes;
@property (nonatomic, strong, nullable) NSMutableArray<UICollectionViewLayoutAttributes *> *itemLayoutAttributes_list;
@property (nonatomic, strong, nullable) UICollectionViewLayoutAttributes *footerLayoutAttributes;

@end

@implementation LBCollectionLayoutSectionModel

- (instancetype)init
{
    self = [super init];
    if (self) {
        self.itemLayoutAttributes_list = [NSMutableArray new];
    }
    return self;
}
@end

@interface LBWaterFallLayout ()

@property (nonatomic, strong) NSMutableArray<NSMutableArray<UICollectionViewLayoutAttributes *> *> *itemLayoutAttributes;
@property (nonatomic, strong) NSMutableArray<UICollectionViewLayoutAttributes *> *originHeaderLayoutAttributes;
@property (nonatomic, strong) NSMutableArray<UICollectionViewLayoutAttributes *> *headerLayoutAttributes;
@property (nonatomic, strong) NSMutableArray<UICollectionViewLayoutAttributes *> *originFooterLayoutAttributes;
@property (nonatomic, strong) NSMutableArray<UICollectionViewLayoutAttributes *> *footerLayoutAttributes;
/// Per section heights.
@property (nonatomic, strong) NSMutableArray<NSNumber *> *heightOfSections;
/// UICollectionView content height.
@property (nonatomic, assign) CGFloat contentHeight;

@property (nonatomic, strong) NSMutableArray <LBCollectionLayoutSectionModel *>* sectionLayoutModels;

@end

@implementation LBWaterFallLayout

@synthesize lb_layoutDelegate;

- (void)prepareLayout
{
    [super prepareLayout];
    [self vv_registerDecorationViews];
    self.contentHeight = 0.0;
    
    @autoreleasepool {
        self.itemLayoutAttributes = nil;
        self.headerLayoutAttributes = nil;
        self.footerLayoutAttributes = nil;
        self.heightOfSections = nil;
        self.sectionLayoutModels = nil;
    }
    
    self.itemLayoutAttributes = [NSMutableArray array];
    self.headerLayoutAttributes = [NSMutableArray array];
    self.footerLayoutAttributes = [NSMutableArray array];
    self.originHeaderLayoutAttributes = [NSMutableArray array];
    self.originFooterLayoutAttributes = [NSMutableArray array];
    self.heightOfSections = [NSMutableArray array];
    self.sectionLayoutModels = [NSMutableArray new];
    
    [self invalidateLayout];
    
    
    UICollectionView *collectionView = self.collectionView;
    NSInteger const numberOfSections = collectionView.numberOfSections;
    
    for (NSInteger section = 0; section < numberOfSections; section++) {
        LBCollectionLayoutSectionModel *sectionLayoutModel = [LBCollectionLayoutSectionModel new];
        CGFloat headerHeight = [self layoutHeaderSection:section sectionLayoutModel:sectionLayoutModel];
        
        NSInteger columnOfSection = 1;
        if ([self.lb_layoutDelegate respondsToSelector:@selector(collectionView:customLayout:columnNumberAtSection:)]) {
            columnOfSection = [self.lb_layoutDelegate collectionView:collectionView customLayout:self columnNumberAtSection:section];
        } else {
#if DEBUG
            NSAssert(NO, @"未设置列数");
#endif
        }
        
        CGFloat offsetOfColumns[columnOfSection];
        [self layoutItemSection:section headerHeight:headerHeight offsetOfColumns:offsetOfColumns columnOfSection:columnOfSection sectionLayoutModel:sectionLayoutModel];
        
        UIEdgeInsets const contentInsetOfSection = [self contentInsetForSection:section];
        CGFloat maxOffsetValue = offsetOfColumns[0];
        for (int i = 1; i < columnOfSection; i++) {
            if (offsetOfColumns[i] > maxOffsetValue) {
                maxOffsetValue = offsetOfColumns[i];
            }
        }
        maxOffsetValue += contentInsetOfSection.bottom;
        
        CGFloat footerHeight = [self layoutFooterSection:section maxOffsetValue:maxOffsetValue sectionLayoutModel:sectionLayoutModel];
        
        CGFloat currentSectionHeight = maxOffsetValue + footerHeight;
        [self.heightOfSections addObject:@(currentSectionHeight)];
        
        self.contentHeight += currentSectionHeight;
        [self.sectionLayoutModels addObject:sectionLayoutModel];
    }
}

- (CGFloat)layoutHeaderSection:(NSInteger)section sectionLayoutModel:(LBCollectionLayoutSectionModel *)sectionLayoutModel
{
    UICollectionView *collectionView = self.collectionView;
    UIEdgeInsets const contentInset = collectionView.contentInset;
    CGFloat const contentWidth = collectionView.bounds.size.width - contentInset.left - contentInset.right;
    
    CGFloat headerHeight = 0.0;
    if ([self.lb_layoutDelegate respondsToSelector:@selector(collectionView:customLayout:referenceSizeForHeaderInSection:)]) {
        CGSize headerSize = [self.lb_layoutDelegate collectionView:collectionView customLayout:self referenceSizeForHeaderInSection:section];
        headerHeight = headerSize.height;
    }
    
    UICollectionViewLayoutAttributes *headerLayoutAttribute = [[UICollectionViewLayoutAttributes alloc] init];
    headerLayoutAttribute.indexPath = [NSIndexPath indexPathForItem:0 inSection:section];
    if (headerHeight > 0) {
        headerLayoutAttribute = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader withIndexPath:[NSIndexPath indexPathForItem:0 inSection:section]];
    }
    
    headerLayoutAttribute.frame = CGRectMake(0.0, self.contentHeight, contentWidth, headerHeight);
    [self.headerLayoutAttributes addObject:headerLayoutAttribute];
    
    [self.originHeaderLayoutAttributes addObject:[headerLayoutAttribute copy]];
    sectionLayoutModel.headerLayoutAttributes = [headerLayoutAttribute copy];
    return headerHeight;
}

- (void)layoutItemSection:(NSInteger)section
             headerHeight:(CGFloat)headerHeight
          offsetOfColumns:(CGFloat [])offsetOfColumns
          columnOfSection:(NSInteger)columnOfSection
       sectionLayoutModel:(LBCollectionLayoutSectionModel *)sectionLayoutModel
{
    UICollectionView *collectionView = self.collectionView;
    
    UIEdgeInsets const contentInset = collectionView.contentInset;
    CGFloat const contentWidth = collectionView.bounds.size.width - contentInset.left - contentInset.right;
    
    UIEdgeInsets const contentInsetOfSection = [self contentInsetForSection:section];
    CGFloat const minimumLineSpacing = [self minimumLineSpacingForSection:section];
    CGFloat const minimumInteritemSpacing = [self minimumInteritemSpacingForSection:section];
    CGFloat const contentWidthOfSection = contentWidth - contentInsetOfSection.left - contentInsetOfSection.right;
    CGFloat const itemWidth = (contentWidthOfSection - (columnOfSection - 1) * minimumInteritemSpacing) / columnOfSection;
    NSInteger numberOfItems = 0;
    if ([collectionView.dataSource respondsToSelector:@selector(collectionView:numberOfItemsInSection:)]) {
        numberOfItems = [collectionView.dataSource collectionView:collectionView numberOfItemsInSection:section];
    }
    
    for (NSInteger i = 0; i < columnOfSection; i++) {
        offsetOfColumns[i] = headerHeight + contentInsetOfSection.top;
    }
    
    NSMutableArray *layoutAttributeOfSection = [NSMutableArray arrayWithCapacity:numberOfItems];
    for (NSInteger item = 0; item < numberOfItems; item++) {
        NSInteger currentColumn = 0;
        for (NSInteger i = 1; i < columnOfSection; i++) {
            if (offsetOfColumns[currentColumn] > offsetOfColumns[i]) {
                currentColumn = i;
            }
        }
        
        NSIndexPath *indexPath = [NSIndexPath indexPathForItem:item inSection:section];
        CGFloat itemHeight = 0;
        if ([self.lb_layoutDelegate respondsToSelector:@selector(collectionView:customLayout:sizeForItemAtIndexPath:)]) {
            CGSize itemSize = [self.lb_layoutDelegate collectionView:collectionView customLayout:self sizeForItemAtIndexPath:indexPath];
            itemHeight = itemSize.height;
        }
        CGFloat x = contentInsetOfSection.left + itemWidth*currentColumn + minimumInteritemSpacing * currentColumn;
        CGFloat y = offsetOfColumns[currentColumn] + (item >= columnOfSection ? minimumLineSpacing : 0.0);
        
        UICollectionViewLayoutAttributes *layoutAttbiture = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
        layoutAttbiture.frame = CGRectMake(x, y + self.contentHeight, itemWidth, itemHeight);
        [layoutAttributeOfSection addObject:layoutAttbiture];
        
        offsetOfColumns[currentColumn] = (y + itemHeight);
    }
    [self.itemLayoutAttributes addObject:layoutAttributeOfSection];
    sectionLayoutModel.itemLayoutAttributes_list = [layoutAttributeOfSection copy];
}

- (CGFloat)layoutFooterSection:(NSInteger)section
                maxOffsetValue:(CGFloat)maxOffsetValue
            sectionLayoutModel:(LBCollectionLayoutSectionModel *)sectionLayoutModel

{
    UICollectionView *collectionView = self.collectionView;
    
    UIEdgeInsets const contentInset = collectionView.contentInset;
    CGFloat const contentWidth = collectionView.bounds.size.width - contentInset.left - contentInset.right;
    
    CGFloat footerHeight = 0.0;
    if ([self.lb_layoutDelegate respondsToSelector:@selector(collectionView:customLayout:referenceSizeForFooterInSection:)]) {
        CGSize footerSize = [self.lb_layoutDelegate collectionView:collectionView customLayout:self referenceSizeForFooterInSection:section];
        footerHeight = footerSize.height;
    }
    UICollectionViewLayoutAttributes *footerLayoutAttribute = [[UICollectionViewLayoutAttributes alloc] init];
    footerLayoutAttribute.indexPath = [NSIndexPath indexPathForItem:0 inSection:section];
    if (footerHeight > 0) {
        footerLayoutAttribute = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionFooter withIndexPath:[NSIndexPath indexPathForItem:0 inSection:section]];
    }
    footerLayoutAttribute.frame = CGRectMake(0.0, self.contentHeight + maxOffsetValue, contentWidth, footerHeight);
    [self.footerLayoutAttributes addObject:footerLayoutAttribute];
    
    [self.originFooterLayoutAttributes addObject:[footerLayoutAttribute copy]];
    sectionLayoutModel.footerLayoutAttributes = [footerLayoutAttribute copy];
    return footerHeight;
}

- (CGSize)collectionViewContentSize
{
    if ([self.lb_layoutDelegate respondsToSelector:@selector(useCustomContentSize)] &&
        [self.lb_layoutDelegate respondsToSelector:@selector(customContentSize)] &&
        [self.lb_layoutDelegate useCustomContentSize]) {
        ///使用自定义contentSize,、
        return [self.lb_layoutDelegate customContentSize];;
    }
    UIEdgeInsets contentInset = self.collectionView.contentInset;
    CGFloat width = CGRectGetWidth(self.collectionView.bounds) - contentInset.left - contentInset.right;
    CGFloat height = MAX(CGRectGetHeight(self.collectionView.bounds), self.contentHeight);
    return CGSizeMake(width, height);
}

- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect
{
    NSMutableArray<UICollectionViewLayoutAttributes *> *result = [NSMutableArray array];
    
    // 悬停
    if (self.sectionHeadersPinToVisibleBounds) {
        for (UICollectionViewLayoutAttributes *attriture in self.headerLayoutAttributes) {
            NSInteger section = attriture.indexPath.section;
            UIEdgeInsets contentInsetOfSection = [self contentInsetForSection:section];
            NSIndexPath *firstIndexPath = [NSIndexPath indexPathForItem:0 inSection:section];
            UICollectionViewLayoutAttributes *itemAttribute = [self layoutAttributesForItemAtIndexPath:firstIndexPath];
            if (!itemAttribute) {
                continue;
            }
            CGFloat headerHeight = CGRectGetHeight(attriture.frame);
            CGRect frame = attriture.frame;
            frame.origin.y = MIN(
                                 MAX(self.collectionView.contentOffset.y + self.contentOffsetY, CGRectGetMinY(itemAttribute.frame)-headerHeight-contentInsetOfSection.top),
                                 CGRectGetMinY(attriture.frame)+[self.heightOfSections[section] floatValue]-headerHeight
                                 );
            attriture.frame = frame;
            attriture.zIndex = (NSIntegerMax/2)+section;
        }
    }
    
    [self.sectionLayoutModels enumerateObjectsUsingBlock:^(LBCollectionLayoutSectionModel * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        NSUInteger section = idx;
        NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:section];
        [self mutableArray:result addDecorationViewAttributWithIndexPath:indexPath];
        if(obj.headerLayoutAttributes
           && !(obj.headerLayoutAttributes.frame.size.height == 0)
           && CGRectIntersectsRect(rect, obj.headerLayoutAttributes.frame)) {
            [result addObject:obj.headerLayoutAttributes];
        }
        
        if (obj.itemLayoutAttributes_list.count > 0) {
            [obj.itemLayoutAttributes_list enumerateObjectsUsingBlock:^(UICollectionViewLayoutAttributes * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
                if(CGRectIntersectsRect(rect, obj.frame)) {
                    [result addObject:obj];
                }
                            
            }];
        }
        
        if(obj.footerLayoutAttributes
           && !(obj.footerLayoutAttributes.frame.size.height == 0)
           && CGRectIntersectsRect(rect, obj.footerLayoutAttributes.frame)) {
            [result addObject:obj.footerLayoutAttributes];
        }
        
    }];
    
    return result;
}

- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
    NSArray<UICollectionViewLayoutAttributes *> *tempArray = (NSArray<UICollectionViewLayoutAttributes *> *)self.itemLayoutAttributes[indexPath.section];
    return tempArray[indexPath.item];
}

- (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath
{
    if ([elementKind isEqualToString:UICollectionElementKindSectionHeader]) {
        return self.headerLayoutAttributes[indexPath.section];
    }
    if ([elementKind isEqualToString:UICollectionElementKindSectionFooter]) {
        return self.footerLayoutAttributes[indexPath.section];
    }
    return nil;
}

- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
{
    if (self.sectionHeadersPinToVisibleBounds) {
        return YES;
    } else {
        return [super shouldInvalidateLayoutForBoundsChange:newBounds];
    }
}

- (UICollectionViewLayoutAttributes *)layoutAttributesForDecorationViewOfKind:(NSString*)decorationViewKind atIndexPath:(NSIndexPath *)indexPath{
    
    UICollectionViewLayoutAttributes* att = [UICollectionViewLayoutAttributes layoutAttributesForDecorationViewOfKind:decorationViewKind withIndexPath:indexPath];
    CGFloat sectionHeight = [self.heightOfSections[indexPath.section] floatValue];
    CGFloat origin_y = [self originYofSection:indexPath.section];
    UIEdgeInsets sectionInsets = [self contentInsetForSection:indexPath.section];
    att.frame = CGRectMake(0, origin_y + sectionInsets.top, self.collectionView.contentSize.width, sectionHeight -(sectionInsets.top + sectionInsets.bottom));
    att.zIndex= -1;
    return att;
}

- (UICollectionViewLayoutAttributes *)originLayoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath
{
    if ([elementKind isEqualToString:UICollectionElementKindSectionHeader]) {
        return self.originHeaderLayoutAttributes[indexPath.section];
    }
    if ([elementKind isEqualToString:UICollectionElementKindSectionFooter]) {
        return self.originFooterLayoutAttributes[indexPath.section];
    }
    return nil;
}

- (CGFloat)itemWidthOfIndexPath:(NSIndexPath *)indexPath
{
    UIEdgeInsets contentInset = self.collectionView.contentInset;
    CGFloat contentWidth = self.collectionView.bounds.size.width - contentInset.left - contentInset.right;
    UIEdgeInsets contentInsetOfSection = [self contentInsetForSection:indexPath.section];
    CGFloat minimumInteritemSpacing = [self minimumInteritemSpacingForSection:indexPath.section];
    NSInteger columnOfSection = [self columnNumAtSection:indexPath.section];
    CGFloat contentWidthOfSection = contentWidth - contentInsetOfSection.left - contentInsetOfSection.right;
    CGFloat itemWidth = (contentWidthOfSection - (columnOfSection - 1) * minimumInteritemSpacing) / columnOfSection;
    return itemWidth;
}

#pragma mark - Private
- (UIEdgeInsets)contentInsetForSection:(NSInteger)section
{
    UIEdgeInsets edgeInsets = UIEdgeInsetsZero;
    if ([self.lb_layoutDelegate respondsToSelector:@selector(collectionView:customLayout:insetForSectionAtIndex:)]) {
        edgeInsets = [self.lb_layoutDelegate collectionView:self.collectionView customLayout:self insetForSectionAtIndex:section];
    }
    return edgeInsets;
}

- (CGFloat)minimumLineSpacingForSection:(NSInteger)section
{
    CGFloat minimumLineSpacing = self.minimumLineSpacing;
    if ([self.lb_layoutDelegate respondsToSelector:@selector(collectionView:customLayout:minimumLineSpacingForSectionAtIndex:)]) {
        minimumLineSpacing = [self.lb_layoutDelegate collectionView:self.collectionView customLayout:self minimumLineSpacingForSectionAtIndex:section];
    }
    return minimumLineSpacing;
}

- (CGFloat)minimumInteritemSpacingForSection:(NSInteger)section
{
    CGFloat minimumInteritemSpacing = self.minimumInteritemSpacing;
    if ([self.lb_layoutDelegate respondsToSelector:@selector(collectionView:customLayout:minimumInteritemSpacingForSectionAtIndex:)]) {
        minimumInteritemSpacing = [self.lb_layoutDelegate collectionView:self.collectionView customLayout:self minimumInteritemSpacingForSectionAtIndex:section];
    }
    return minimumInteritemSpacing;
}

- (NSInteger)columnNumAtSection:(NSInteger)section
{
    NSInteger columnOfSection = 1;
    if ([self.lb_layoutDelegate respondsToSelector:@selector(collectionView:customLayout:columnNumberAtSection:)]) {
        columnOfSection = [self.lb_layoutDelegate collectionView:self.collectionView customLayout:self columnNumberAtSection:section];
        return columnOfSection;
    } else {
#if DEBUG
        NSAssert(NO, @"未设置列数");
#endif
    }
    return 1;
}

- (void)vv_registerDecorationViews
{
    if (self.lb_layoutDelegate
        && [self.lb_layoutDelegate respondsToSelector:@selector(decorationViewClasses)]) {
        NSArray <Class>*classes = [self.lb_layoutDelegate decorationViewClasses];
        for (Class decorationViewClass in classes) {
            if ([decorationViewClass isSubclassOfClass:[UICollectionReusableView class]]) {
                [self registerClass:decorationViewClass forDecorationViewOfKind:NSStringFromClass([decorationViewClass class])];
            }
        }
    }
}

- (void)mutableArray:(NSMutableArray *)results addDecorationViewAttributWithIndexPath:(NSIndexPath *)indexPath
{
    if (self.lb_layoutDelegate
        && [self.lb_layoutDelegate respondsToSelector:@selector(decorationViewClassOfIndexPath:)]) {
        Class decorationViewClass = [self.lb_layoutDelegate decorationViewClassOfIndexPath:indexPath];
        if ([decorationViewClass isSubclassOfClass:[UICollectionReusableView class]]) {
            [results addObject:[self layoutAttributesForDecorationViewOfKind: NSStringFromClass([decorationViewClass class]) atIndexPath:indexPath]];
        }
    }
}

- (CGFloat)originYofSection:(NSUInteger)section
{
    CGFloat origin_y = 0;
    for (NSUInteger index = 0; index < section; index++) {
      CGFloat sectionHeight = [self.heightOfSections[index] floatValue];
        origin_y += sectionHeight;
    }
    return origin_y;
}

@end

使用方法

pod 'LBWaterFallLayout'

///初始化,并设置代理
- (UICollectionView *)collectionView
{
    if (!_collectionView) {
        LBWaterFallLayout *layout = [[LBWaterFallLayout alloc] init];
        layout.lb_layoutDelegate = self;
         
         _collectionView = [[UICollectionView alloc]initWithFrame:CGRectZero collectionViewLayout:layout];
         if (@available(iOS 10.0, *)) {
             _collectionView.prefetchingEnabled = NO;
         } else {
             // Fallback on earlier versions
         }
         _collectionView.scrollEnabled = YES;
         _collectionView.dataSource = self;
         _collectionView.delegate = self;
         _collectionView.showsVerticalScrollIndicator = NO;
         _collectionView.showsHorizontalScrollIndicator = NO;
        if (@available(iOS 11.0, *)) {
            _collectionView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
        } else {
            // Fallback on earlier versions
        }
         _collectionView.backgroundColor = [UIColor redColor];
        [_collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:NSStringFromClass([UICollectionViewCell class])];
    }
    return _collectionView;
}

/// 实现代理方法
#pragma mark - VVCollectionCustomLayoutDelegate
/// 每个区多少列
- (NSInteger)collectionView:(UICollectionView *)collectionView customLayout:(UICollectionViewLayout *)collectionViewLayout columnNumberAtSection:(NSInteger )section
{
    LBSectionConfigModel *layout = self.sectionConfigArray[section];
    return layout.colomnsNum;
}

- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView customLayout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section
{
    LBSectionConfigModel *layout = self.sectionConfigArray[section];
    return layout.sectionEdgeInsets;
}

- (CGFloat)collectionView:(UICollectionView *)collectionView customLayout:(UICollectionViewLayout *)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section
{
    LBSectionConfigModel *layout = self.sectionConfigArray[section];
    return layout.lineSpace;
}

- (CGFloat)collectionView:(UICollectionView *)collectionView customLayout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section
{
    LBSectionConfigModel *layout = self.sectionConfigArray[section];
    return layout.itemSpace;
}

- (CGSize)collectionView:(UICollectionView *)collectionView customLayout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section
{
    LBSectionConfigModel *layout = self.sectionConfigArray[section];
    return CGSizeMake(collectionView.contentSize.width, layout.headerHeight);
}

- (CGSize)collectionView:(UICollectionView *)collectionView customLayout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section
{
    LBSectionConfigModel *layout = self.sectionConfigArray[section];
    return CGSizeMake(collectionView.contentSize.width, layout.footerHeight);
}

- (CGSize)collectionView:(UICollectionView *)collectionView customLayout:(LBWaterFallLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
    NSString *title = self.dataArray[indexPath.item % self.dataArray.count];
    CGFloat height = [title boundingRectWithSize:CGSizeMake(CGRectGetWidth(self.view.bounds)/2 - 30, 200) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:20]} context:nil].size.height;
    return CGSizeMake(CGRectGetWidth(self.view.bounds)/2 - 30, height + 150);
    //return CGSizeMake(, 100);
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容