文章目录

  • 自定义UICollectionViewLayout 流程
  • 一 在 prepareLayout 创建布局属性
  • 二、 重写 layoutAttributesForElementsInRect 在里面返回创建的布局属性数组
  • 三、 重写 collectionViewContentSize方法,并在里面返回总内容的size
  • 注意点
  • 一、我们自定义layout 布局在创建属性布局的时候需要记录一个当前的origin
  • 二、 如果我们自定义的layout 重写prepareLayout,则我们必须重写collectionViewContentSize 方法
  • 三 、 自定义layout 中系统不支持直接获取某个item的size ,需要我们自定义协议并设置代理获取
  • 四、 总结起来就是 : 每个item frame 的origin 需要layout 自己记录维护,size 需要通过代理获取
  • 完整代码
  • .h
  • .m


自定义UICollectionViewLayout 流程

一 在 prepareLayout 创建布局属性

//创建layout类,继承于系统类 UICollectionViewFlowLayout
@interface TPShareCollectionViewFlowLayout : UICollectionViewFlowLayout

@end
/// 重写 prepareLayout 方法,并在该方法里面创建布局属性
- (void)prepareLayout
{
    [super prepareLayout];
    [self configLayout];
}

/// 创建布局的方法
- (void)configLayout
{
///添加了一个 layoutAttributesArray 数组,用来持有创建的布局属性,并在 layoutAttributesForElementsInRect 返回
    [self.layoutAttributesArray removeAllObjects];
    NSInteger count = [self.collectionView.dataSource collectionView:self.collectionView numberOfItemsInSection:0];
    CGFloat left = 0;
    for (NSInteger i = 0; i < count ;i ++) {
        // 创建布局属性
        UICollectionViewLayoutAttributes *layoutAttributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:[NSIndexPath indexPathForRow:i inSection:0]];
        /// 这里是一个自定义的代理方法,通过改方法获取到相应item的size
        CGSize size = [self.delegate collection:self.collectionView sizeForItemAtIndexPath:[NSIndexPath indexPathForItem:i inSection:0]];
        CGRect frame = CGRectMake(left, 0, size.width, size.height);
        layoutAttributes.frame =  frame;
        [self.layoutAttributesArray addObject:layoutAttributes];
        if (i == count - 1) {
            self.width = left + size.width;
        } else {
            left += (size.width + self.minimumLineSpacing);
        }
    }
}

二、 重写 layoutAttributesForElementsInRect 在里面返回创建的布局属性数组

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
    return self.layoutAttributesArray;
}

三、 重写 collectionViewContentSize方法,并在里面返回总内容的size

注意,我们需要在自定义layout 中管理记录当前布局的size,用来返回,因为我这里高度是固定的,所以自己记录的内容的宽度
即self.width 这个自定义属性

- (CGSize)collectionViewContentSize
{
    return CGSizeMake(self.width, self.collectionView.bounds.size.height);
}

注意点

一、我们自定义layout 布局在创建属性布局的时候需要记录一个当前的origin

因为我这里自定义的y是固定的,都是0(一个横向的自定义layout),所以只是维护记录了一个当前的x位置
即left

left += (size.width + self.minimumLineSpacing);

二、 如果我们自定义的layout 重写prepareLayout,则我们必须重写collectionViewContentSize 方法

**collectionViewContentSize 默认会根据系统的布局属性数组(我们不知道属性名字)layout. 自身的总size **

不重写collectionViewContentSize 方法的话,layout 还是使用系统的数组来决定size, 因为我们重写了prepareLayout 方法,所以系统的数组并没有创建,这样就无法返回正确的size了,所以我们需要自己在创建布局属性的时候记录一下当前的size,因为我这里高度是固定的,所以只记录宽度就可以了

if (i == count - 1) {
      self.width = left + size.width;
   }
- (CGSize)collectionViewContentSize
{
    return CGSizeMake(self.width, self.collectionView.bounds.size.height);
}

三 、 自定义layout 中系统不支持直接获取某个item的size ,需要我们自定义协议并设置代理获取

@protocol TPShareCollectionViewFlowLayoutProtocol <NSObject>

- (CGSize)collection:(UICollectionView *)collectionView sizeForItemAtIndexPath:(NSIndexPath *)indexPath;

@end
UICollectionViewLayoutAttributes *layoutAttributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:[NSIndexPath indexPathForRow:i inSection:0]];
        CGSize size = [self.delegate collection:self.collectionView sizeForItemAtIndexPath:[NSIndexPath indexPathForItem:i inSection:0]];
        CGRect frame = CGRectMake(left, 0, size.width, size.height);

四、 总结起来就是 : 每个item frame 的origin 需要layout 自己记录维护,size 需要通过代理获取

完整代码

.h

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@protocol TPShareCollectionViewFlowLayoutProtocol <NSObject>

- (CGSize)collection:(UICollectionView *)collectionView sizeForItemAtIndexPath:(NSIndexPath *)indexPath;

@end

@interface TPShareCollectionViewFlowLayout : UICollectionViewFlowLayout

@property (nonatomic, weak) id <TPShareCollectionViewFlowLayoutProtocol> delegate;

@end

NS_ASSUME_NONNULL_END

.m

#import "TPShareCollectionViewFlowLayout.h"

@interface TPShareCollectionViewFlowLayout ()

@property (nonatomic, assign) CGFloat width;

@property (nonatomic, strong) NSMutableArray *layoutAttributesArray;

@end

@implementation TPShareCollectionViewFlowLayout

- (void)prepareLayout
{
    [super prepareLayout];
    [self configLayout];
}

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
    return self.layoutAttributesArray;
}

- (void)configLayout
{
    [self.layoutAttributesArray removeAllObjects];
    NSInteger count = [self.collectionView.dataSource collectionView:self.collectionView numberOfItemsInSection:0];
    CGFloat left = 0;
    for (NSInteger i = 0; i < count ;i ++) {
        // 创建布局属性
        UICollectionViewLayoutAttributes *layoutAttributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:[NSIndexPath indexPathForRow:i inSection:0]];
        CGSize size = [self.delegate collection:self.collectionView sizeForItemAtIndexPath:[NSIndexPath indexPathForItem:i inSection:0]];
        CGRect frame = CGRectMake(left, 0, size.width, size.height);
        layoutAttributes.frame =  frame;
        [self.layoutAttributesArray addObject:layoutAttributes];
        if (i == count - 1) {
            self.width = left + size.width;
        } else {
            left += (size.width + self.minimumLineSpacing);
        }
    }
}

- (CGSize)collectionViewContentSize
{
    return CGSizeMake(self.width, self.collectionView.bounds.size.height);
}

- (NSMutableArray *)layoutAttributesArray
{
    if (!_layoutAttributesArray) {
        _layoutAttributesArray = [NSMutableArray array];
    }
    return _layoutAttributesArray;
}

@end