Objective-C的UICollectionView(集合视图)学习笔记

UICollectionView - 小白级别的应用

开篇即代码,这是UICollectionView最简单的应用效果了,十分符合我的水准:

//
//  TSChannelFourViewController.m

#import "TSChannelFourViewController.h"

/**
 模型
 */
@interface CategoryItemObject : NSObject

@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSArray<NSString *> *categoryArray;

@end

@implementation CategoryItemObject

- (instancetype)init {
    self = [super init];
    if (self) {
        self.title = @"分类名称";
        self.categoryArray = @[@"分类一",@"分类二",@"分类三",@"分类四",@"分类五",@"分类六",@"分类七",@"分类八",@"分类九",];
    }
    return self;
}

@end

/**
 页眉补充视图
 */
UIKIT_EXTERN NSString *const HeaderViewReuseIdentifier;
@interface HeaderView : UICollectionReusableView

@property (nonatomic, strong) UILabel *titleLabel;

@end

NSString *const HeaderViewReuseIdentifier = @"HeaderViewReuseIdentifier";
@implementation HeaderView

- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        self.titleLabel = [[UILabel alloc]initWithFrame:self.bounds];
        self.titleLabel.font = [UIFont systemFontOfSize:15];
        self.titleLabel.textColor = [UIColor blackColor];
        self.titleLabel.textAlignment = NSTextAlignmentCenter;
        [self addSubview:self.titleLabel];
    }
    return self;
}

@end

/**
 单元格
 */
UIKIT_EXTERN NSString *const CategoryCellReuseIdentifier;
@interface CategoryCell : UICollectionViewCell

@property (nonatomic, strong) UILabel *categoryNameLabel;

@end

NSString *const CategoryCellReuseIdentifier = @"CategoryCellReuseIdentifier";
@implementation CategoryCell

- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        self.layer.cornerRadius = 22.0;
        self.layer.borderWidth = 1.0;
        self.categoryNameLabel = [[UILabel alloc]initWithFrame:self.bounds];
        self.categoryNameLabel.font = [UIFont systemFontOfSize:13];
        self.categoryNameLabel.textAlignment = NSTextAlignmentCenter;
        [self.contentView addSubview:self.categoryNameLabel];
    }
    return self;
}

@end

/**
 控制器
 */
@interface TSChannelFourViewController ()<UICollectionViewDataSource,UICollectionViewDelegate>

@property (nonatomic, strong) UICollectionView *collectionView;
@property (nonatomic, strong) NSMutableArray<CategoryItemObject *> *dataSource;

@end

@implementation TSChannelFourViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    //流布局
    UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc]init];
    flowLayout.scrollDirection = UICollectionViewScrollDirectionVertical;
    flowLayout.minimumLineSpacing = 15;
    flowLayout.minimumInteritemSpacing = 15;
    flowLayout.sectionInset = UIEdgeInsetsMake(15, 15, 15, 15);
    flowLayout.headerReferenceSize = CGSizeMake(45, 45);
    //集合视图
    self.collectionView = [[UICollectionView alloc]initWithFrame:CGRectZero collectionViewLayout:flowLayout];
    self.collectionView.dataSource = self;
    self.collectionView.delegate = self;
    self.collectionView.allowsMultipleSelection = YES;
    [self.collectionView registerClass:CategoryCell.class forCellWithReuseIdentifier:CategoryCellReuseIdentifier];
    [self.collectionView registerClass:HeaderView.class forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:HeaderViewReuseIdentifier];
    [self.view addSubview:self.collectionView];
    self.collectionView.translatesAutoresizingMaskIntoConstraints = NO;
    [NSLayoutConstraint activateConstraints:@[
        [self.collectionView.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor],
        [self.collectionView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
        [self.collectionView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
        [self.collectionView.bottomAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor],
    ]];
}

/// 数据源
- (NSMutableArray<CategoryItemObject *> *)dataSource {
    if (_dataSource == nil) {
        _dataSource = [[NSMutableArray alloc]init];
        for (int i = 0; i < 3; i++) {
            CategoryItemObject *categoryItem = [[CategoryItemObject alloc]init];
            [_dataSource addObject:categoryItem];
        }
    }
    return _dataSource;
}

#pragma mark - UICollectionViewDataSource
//有几节单元格
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
    return self.dataSource.count;
}

//每节有多少个单元格
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
    CategoryItemObject *categoryItem = self.dataSource[section];
    return categoryItem.categoryArray.count;
}

//配置单元格
- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    NSString *categoryName = self.dataSource[indexPath.section].categoryArray[indexPath.row];
    CategoryCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:CategoryCellReuseIdentifier forIndexPath:indexPath];
    cell.categoryNameLabel.text = categoryName;
    return cell;
}

//配置页眉补充视图
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {
    if ([kind isEqualToString:UICollectionElementKindSectionHeader]) {
        CategoryItemObject *categoryItem = self.dataSource[indexPath.section];
        HeaderView *headerView = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:HeaderViewReuseIdentifier forIndexPath:indexPath];
        headerView.titleLabel.text = categoryItem.title;
        return headerView;
    }
    return nil;
}

#pragma mark - UICollectionViewDelegate
//单元格已被选择
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
    CategoryCell *cell = (CategoryCell *)[collectionView cellForItemAtIndexPath:indexPath];
    cell.backgroundColor = [UIColor colorWithRed:226 / 255.0f green:240 / 255.0f blue:253 / 255.0f alpha:1.0];
    cell.layer.borderColor = [UIColor blueColor].CGColor;
    cell.categoryNameLabel.textColor = [UIColor blueColor];
}

//单元格已被取消选择
- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath{
    CategoryCell *cell = (CategoryCell *)[collectionView cellForItemAtIndexPath:indexPath];
    cell.backgroundColor = [UIColor whiteColor];
    cell.layer.borderColor = [UIColor blackColor].CGColor;
    cell.categoryNameLabel.textColor = [UIColor blackColor];
}

#pragma mark - UICollectionViewDelegateFlowLayout
//每个单元格的大小
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath{
    UICollectionViewFlowLayout *flowLayout = (UICollectionViewFlowLayout *)collectionView.collectionViewLayout;
    CGFloat cellWidth = (collectionView.bounds.size.width - flowLayout.minimumInteritemSpacing * 2 - flowLayout.sectionInset.left - flowLayout.sectionInset.right) / 3;
    return CGSizeMake(floor(cellWidth), 44);
}

@end
截屏2022-08-16 17.22.38.png

UICollectionView - 集合视图

UICollectionView(以下称为集合视图)继承自UIScrollView,是在IOS6中开放的为了增强网格视图开发的视图API。

集合视图有两个重要协议:UICollectionViewDataSource(数据源协议)与UICollectionViewDelegate(代理协议),UICollectionViewDataSource(数据源协议)用来管理数据和为集合视图提供单元格,UICollectionViewDelegate(代理协议)用于管理用户与集合视图中的单元格的交互。

UICollectionViewCell(单元格)是集合视图要呈现的最小数据单位,UICollectionViewCell(单元格)就是一个视图,没有样式和风格定义,可以在内部放置其他视图或控件。自定义一个UICollectionViewCell(单元格)类,它需要继承UICollectionViewCell。

除了UICollectionViewCell(单元格)之外,集合视图还可以使用其他类型的补充视图显示数据,例如这些补充视图可以是与每一节单元格分开但仍传达信息的节页眉视图和节页脚视图。对补充视图的支持是可选的,由集合视图的布局对象负责定义这些视图的位置。

UICollectionViewLayout用于为集合视图生成布局信息的抽象基类,确定单元格、补充视图和装饰视图在集合视图边界内的位置。UICollectionViewFlowLayout类是UICollectionViewLayout类的子类,提供了流式布局。

集合视图的组成

集合视图有4个重要的组成部分,分别为:

  1. 单元格:即视图中的一个单元格。
  2. 节:即集合视图中的一个行数据,由多个单元格构成。
  3. 补充视图:即节的页眉和页脚。
  4. 装饰视图:集合视图中的背景视图。
UICollectionView的一些常用属性
@property (nonatomic, strong) UICollectionViewLayout *collectionViewLayout;

属性描述用于组织集合视图单元格的布局对象,将新的布局对象指定给此属性会导致将新布局(不带动画)应用于集合视图的单元格。

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

属性描述充当集合视图的代理的对象,代理对象必须采用UICollectionViewDelegate协议,集合视图维护对委托对象的弱引用,代理对象负责管理选择行为和与单个单元格的交互。

@property (nonatomic, weak, nullable) id <UICollectionViewDataSource> dataSource;

属性描述为集合视图提供数据源的对象,数据源对象必须采用UICollectionViewDataSource协议,集合视图维护对数据源对象的弱引用。

@property (nonatomic, strong, nullable) UIView *backgroundView;

属性描述提供背景外观的视图,此属性中的视图(如果有)位于所有其他内容的下面,并自动调整大小以填充集合视图的整个边界。背景视图不与集合视图的其他内容一起滚动,集合视图维护对背景视图对象的强引用,默认情况下此属性为nil,为此属性设置的视图显示与否也依赖于集合视图是否设置了数据源对象。

@property (nonatomic) BOOL allowsSelection; 

属性描述指示用户是否可以在集合视图中选择单元格的布尔值。如果此属性的值为YES(默认值),则用户可以选择单元格。如果希望对单元格的选择进行更细粒度的控制,则必须提供代理对象并实现UICollectionViewDelegate协议的适当方法。

@property (nonatomic) BOOL allowsMultipleSelection;

属性描述一个布尔值,用于确定用户是否可以在集合视图中选择多个单元格。此属性的默认值为NO,当此属性的值为YES时,单击单元格将其添加到当前选择中(假设代理允许选择该单元格),再次点击单元格将其从选择中移除。

@property (nonatomic, readonly) NSInteger numberOfSections;

函数描述 :只读属性,返回集合视图显示的节数

@property (nonatomic, readonly) NSArray<__kindof UICollectionViewCell *> *visibleCells;

属性描述 :只读属性,返回集合视图当前显示的可见单元格对象数组,如果没有可见的单元格,则返回空数组。

@property (nonatomic, readonly) NSArray<NSIndexPath *> *indexPathsForVisibleItems;

属性描述 :只读属性,集合视图中可见单元格的NSIndexPath对象数组。该数组未经过排序,每个NSIndexPath对象都对应于集合视图中的一个可见单元格。如果没有可见单元格,则此属性的值为空数组。此数组不包含任何当前可见的补充视图。

UICollectionView的一些常用函数
- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout NS_DESIGNATED_INITIALIZER;

函数描述使用指定的框架矩形(frame)和集合视图单元格的布局对象(layout)初始化并返回新分配的集合视图对象。当以编程方式初始化集合视图对象时使用此方法,此方法是指定的初始化方法。

参数 :

frame : 集合视图的框架矩形,以点为单位,框架的原点相对于要在其中添加它的父视图,此frame在初始化期间传递给父类。

layout : 用于组织单元格的布局对象,集合视图存储对指定对象的强引用,不能为nil。

返回值 : 初始化的集合视图对象,如果无法创建该对象,则为nil。

例如 :

UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
UICollectionView *collectionView = [[UICollectionView alloc]initWithFrame:CGRectZero collectionViewLayout:layout];
- (void)registerClass:(nullable Class)cellClass forCellWithReuseIdentifier:(NSString *)identifier;

函数描述注册一个类以用于创建新的集合视图单元格。在调用集合视图的dequeueReusableCellWithReuseIdentifier:forIndexPath:方法之前,必须使用此方法或registerNib:forCellWithReuseIdentifier:方法告诉集合视图如何创建给定类型的新单元格。如果指定类型的单元格当前不在重用队列中,则集合视图将使用提供的信息自动创建新的单元格对象。

如果两个相同的重用标识符注册了类或nib文件,则后注册的类将替换之前使用该重用标识符注册的类,如果要从指定的重用标识符中注销类,可以调用该函数传入指定的重用标识符并为cellClass参数指定nil。

参数 :

cellClass : 要在集合视图中使用的单元格的类。

identifier : 要与指定类关联的重用标识符。此参数不能为nil,也不能为空字符串。

例如 :

[self.collectionView registerClass:[GoodsSalesDiscountItem class] forCellWithReuseIdentifier:@"item"];
- (void)registerClass:(nullable Class)viewClass forSupplementaryViewOfKind:(NSString *)elementKind withReuseIdentifier:(NSString *)identifier;  

函数描述注册一个类,用于为集合视图创建补充视图。在调用集合视图的dequeueReusableSupplementaryViewOfKind:withReuseIdentifier:forIndexPath:方法之前,必须使用此方法或registerNib:forSupplementaryViewOfKind:withReuseIdentifier:method来告诉集合视图如何创建给定类型的补充视图。如果指定类型的视图当前不在重用队列中,则集合视图将使用提供的信息自动创建视图对象。

如果两个相同的重用标识符注册了类或nib文件,则后注册的类将替换之前使用该重用标识符注册的类,如果要从指定的重用标识符中注销类,可以调用该函数传入指定的重用标识符并为viewClass参数指定nil。

参数 :

viewClass : 用于补充视图的类。

elementKind : 要创建的补充视图的类型。此值由布局对象定义,此参数不能为nil。

identifier : 要与指定类关联的重用标识符。此参数不能为nil,也不能为空字符串。

例如 :

[self.collectionView registerClass:[HeaderView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"headerView"];

注:定义于UICollectionViewLayout中的补充视图类型:

// 标识给定节的页眉补充视图
UIKIT_EXTERN NSString *const UICollectionElementKindSectionHeader API_AVAILABLE(ios(6.0));
// 标识给定节的页脚补充视图
UIKIT_EXTERN NSString *const UICollectionElementKindSectionFooter API_AVAILABLE(ios(6.0));
- (__kindof UICollectionViewCell *)dequeueReusableCellWithReuseIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath;

函数描述返回由其标识符定位的可重用单元格对象,当要求为集合视图提供新单元格时,从数据源对象代理函数中调用此方法,如果有一个可复用的单元格可用,此方法会使现有单元格出列,或者根据先前注册的类或NIB文件创建一个新的单元格。

在调用此方法之前,必须使用registerClass:forCellWithReuseIdentifier:或register nib:forCellWithReuseIdentifier:方法注册类或nib文件

如果您为指定的标识符注册了一个类,并且必须创建一个新的单元格,则此方法通过调用其initWithFrame:方法初始化该单元格。对于基于nib的单元,此方法从提供的nib文件加载单元对象。如果现有的单元可用于重用,则该方法调用单元格的PraseReFuleRead方法。

参数 :

identifier : 指定单元格的重用标识符。此参数不能为nil。

indexPath : 指定单元格位置的索引路径。数据源在请求单元格时接收此信息,并应将其传递出去。此方法使用索引路径根据单元格在集合视图中的位置执行其他配置。

返回值 :有效的UICollectionViewCell对象。

例如 :

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    GoodsSalesDiscountItem *item = [collectionView dequeueReusableCellWithReuseIdentifier:@"item" forIndexPath:indexPath];
    return item;
}
- (__kindof UICollectionReusableView *)dequeueReusableSupplementaryViewOfKind:(NSString *)elementKind withReuseIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath;

函数描述返回可重用的补充视图,该视图按其标识符(identifier)和类型(elementKind)定位。当要求为集合视图提供新的补充视图时,数据源对象代理函数中调用此方法,如果现有补充视图可复用,则此方法使现有补充视图出列,或者根据之前注册的类或 nib 文件创建新补充视图。

在调用此方法之前,必须使用registerClass:forSupplementaryViewOfKind:withReuseIdentifier:或registerNib:forSupplementaryViewOfKind:withReuseIdentifier:方法注册类或nib文件。还可以使用registerClass:forDecorationViewOfKind:或registerNib:forDecorationViewOfKind:方法向布局对象注册一组默认补充视图。

参数 :

elementKind : 要检索的补充视图的类型。此值由布局对象定义。此参数不能为nil。

identifier : 指定视图的重用标识符。此参数不能为nil。

indexPath : 指定补充视图在集合视图中的位置的索引路径。数据源在请求视图时接收此信息,并应将其传递出去。此方法使用信息根据视图在集合视图中的位置执行附加配置。

返回值 : 有效的UICollectionReusableView对象。

例如 :

- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {
    if ([kind isEqualToString:UICollectionElementKindSectionHeader]) {
        HeaderView *headerView = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"headerView" forIndexPath:indexPath];     
        return headerView;
    }
    return nil;
}
- (void)reloadData;

函数描述重新加载集合视图的所有数据。当需要重新加载集合视图中的所有单元格时,请调用此方法。调用此方法需要谨慎,为了提高效率,集合视图只显示可见的单元格和补充视图,而调用该方法将导致集合视图放弃任何当前可见的单元格(包括占位符),并基于数据源对象的当前状态重新创建单元格。如果重新加载导致集合数据收缩,则集合视图会相应地调整其滚动偏移。不应在插入或删除项目的动画块中间调用此方法,插入和删除会自动导致集合的数据得到适当更新。

- (NSInteger)numberOfItemsInSection:(NSInteger)section;

函数描述 :返回指定节中的单元格数量

参数 :

section : 要对其进行单元格计数的节的索引。

返回值 : 指定节中的单元格数量。

- (nullable UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath;

函数描述返回位于指定索引路径的单元格的布局信息。要检索特定单元格的布局信息,应该始终使用此方法,而不是直接查询布局对象。

参数 :

indexPath : 项的索引路径。

返回值 : 项的布局属性,如果指定路径上不存在项,则为nil。

- (nullable UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;

函数描述返回位于指定索引路径的补充视图的布局信息。要检索特定补充视图的布局信息,应该始终使用此方法,而不是直接查询布局对象。

参数 :

kind : 指定所需布局属性定义的补充视图类型的字符串,布局类负责定义它们支持的补充视图的类型。

indexPath : 补充视图的索引路径,此值的解释取决于布局如何实现视图。例如,与节关联的视图可能只包含节的值。

返回值 : 返回补充视图的布局属性,如果指定的补充视图不存在,则返回nil。

- (nullable NSIndexPath *)indexPathForItemAtPoint:(CGPoint)point;

函数描述返回集合视图中指定点所在的单元格的索引路径,此方法依赖关联的布局对象提供的布局信息来确定包含该点的单元格。

参数 :

point : 集合视图坐标系中的点。

返回值 : 在指定点上的单元格的索引路径,如果在指定点上找不到单元格,则为nil。

- (nullable NSIndexPath *)indexPathForCell:(UICollectionViewCell *)cell;

函数描述 : 返回指定单元格的索引路径

参数 :

cell : 需要其索引路径的单元格对象。

返回值 : 单元格的索引路径,如果指定的单元格不在集合视图中,则为nil。

- (nullable UICollectionViewCell *)cellForItemAtIndexPath:(NSIndexPath *)indexPath;

函数描述 : 返回指定索引路径处的可见单元格对象

参数 :

indexPath : 指定单元格的节(indexPath.section)和项(indexPath.item、indexPath.row)的索引路径。

返回值 : 对应索引路径处的单元格对象,如果单元格不可见或indexPath超出范围,则为nil。

- (void)scrollToItemAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UICollectionViewScrollPosition)scrollPosition animated:(BOOL)animated;

函数描述滚动集合视图内容,直到指定的单元格可见

参数 :

indexPath : 要滚动到视图中的单元格的索引路径。

scrollPosition : 一个选项,指定滚动完成时单元格应放置在何处。有关可能值的列表,参阅UICollectionViewScrollPosition。

animated : 指定“YES”设置滚动行为的动画,或指定“NO”立即调整滚动视图的可见内容。

  • UICollectionViewScrollPosition的枚举值:
typedef NS_OPTIONS(NSUInteger, UICollectionViewScrollPosition) {
    //不要将项目滚动到视图中。
    UICollectionViewScrollPositionNone                 = 0,
    //滚动以使项目位于集合视图边界的顶部。
    //此选项与UICollectionViewScrollPositionCenteredVertical
    //和UICollectionViewScrollPositionBottom选项互斥。
    UICollectionViewScrollPositionTop                  = 1 << 0, 
    //滚动以使项目在集合视图中垂直居中。
    //此选项与UICollectionViewScrollPositionTop
    //和UICollectionViewScrollPositionBottom选项互斥
    UICollectionViewScrollPositionCenteredVertically   = 1 << 1, 
    //滚动以使项目位于集合视图边界的底部。
    //此选项与UICollectionViewScrollPositionTop
    //和UICollectionViewScrollPositionCenteredVertical选项互斥。
    UICollectionViewScrollPositionBottom               = 1 << 2,
    //滚动以使项目位于集合视图边界的左边缘。
    //此选项与uiCollectionViewScrollPositionCenteredHorizontal
    //和UICollectionViewScrollPositionRight选项互斥。
    UICollectionViewScrollPositionLeft                 = 1 << 3, 
    //滚动以使项目在集合视图中水平居中。
    //此选项与UICollectionViewScrollPositionLeft
    //和UICollectionViewScrollPositionRight选项互斥。
    UICollectionViewScrollPositionCenteredHorizontally = 1 << 4, 
    //滚动以使项目位于集合视图边界的右边缘。
    //此选项与UICollectionViewScrollPositionLeft
    //和uiCollectionViewScrollPositionCenteredHorizontal选项互斥。
    UICollectionViewScrollPositionRight                = 1 << 5 
};

UICollectionViewDataSource - 集合视图数据源

采用UICollectionViewDataSource协议的对象负责提供集合视图所需的数据和视图。数据源对象表示应用程序的数据模型,并根据需要向集合视图提供信息。它还处理集合视图用于显示数据的单元格和补充视图的创建和配置。所有数据源对象都必须实现collectionView:numberOfItemsInSection:和collectionView:cellForItemAtIndexPath:方法。这些方法负责返回集合视图中的单元格数量以及单元格本身。协议的其余方法是可选的,只有在集合视图将单元格组织为多个节或为给定节提供页眉补充试图和页脚补充视图时才需要。

UICollectionViewDataSource中提供的常用函数
//提供视图中节的个数,这个方法需要注意数据的行是否能与每一行有几个单元格整除,不能整除时要多加一行
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView{
}

函数描述询问数据源对象集合视图中的节数。如果不实现此方法,则集合视图使用默认值1。

参数 :

collectionView : 请求此信息的集合视图。

返回值 : collectionView中的节数。

//每一节有几个单元格
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
}

函数描述 :询问数据源对象指定节中的单元格数量

参数 :

collectionView : 请求此信息的集合视图。

section : 标识collectionView中节的索引号,此索引值基于0。

返回值 : 节中的单元格数量。

//为某个单元格提供显示数据
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
}

函数描述向数据源对象询问与集合视图中指定项(indexPath.item、indexPath.row)对应的单元格,此方法的实现负责为给定项(indexPath.item、indexPath.row)创建、配置和返回适当的单元格。为此,可以调用集合视图的dequeueReusableCellWithReuseIdentifier:forIndexPath:方法并传递与所需单元格类型对应的重用标识符。该方法始终返回有效的单元格对象。接收单元格后,应设置与相应项(indexPath.item、indexPath.row)的数据相对应的任何属性,执行任何其他所需配置,并返回单元格。

不需要在集合视图的边界内设置单元格的位置。集合视图使用每个单元格的布局对象提供的布局属性自动设置每个单元格的位置。

如果集合视图上的prefetchingEnabled设置为YES,则在单元格出现之前调用此方法。使用collectionView:willDisplayCell:forItemAtIndexPath:delegate方法对单元格的外观进行任何更改,以反映其视觉状态(如选择)。

此方法必须始终返回有效的视图对象

参数 :

collectionView : 请求此信息的集合视图。

indexPath : 指定项位置的索引路径。

返回值 : 已配置的单元格对象。此方法不能返回nil,nil会引发异常。

//为补充视图提供显示数据
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath{
}

函数描述询问数据源对象提供要在集合视图中显示的补充视图,此方法的实现负责创建、配置和返回所请求的适当补充视图。为此,可以调用集合视图的dequeueReusableSupplementaryViewOfKind:withReuseIdentifier:forIndexPath:方法并传递与所需视图对应的信息。该方法始终返回有效的视图对象。接收到视图后,应设置与要显示的数据相对应的任何属性,执行任何其他所需配置,并返回视图。

不需要在集合视图的边界内设置补充视图的位置,集合视图使用其布局对象提供的布局属性设置每个视图的位置。

此方法必须始终返回有效的视图对象。如果在特定情况下不需要补充视图,则布局对象不应为该视图创建属性。或者,可以通过将相应属性的hidden属性设置为YES或将属性的alpha属性设置为0来隐藏视图。若要隐藏流布局中的页眉补充视图和页脚补充视图,还可以将这些视图的宽度和高度设置为0

参数 :

collectionView : 请求此信息的集合视图。

kind : 要提供的那种补充视图类型。此字符串的值由支持补充视图的布局对象定义。

indexPath : 指定新补充视图位置的索引路径。

返回值 : 已配置的补充视图对象。此方法不能返回nil,nil会引发异常。

UICollectionViewDelegate - 集合视图代理

UICollectionViewDelegate协议定义了一些方法,管理集合视图中单元格的选择和突出显示,并对这些单元格执行操作。本协议的方法都是可选的,该协议的许多方法都将NSIndexPath对象作为参数。为了支持集合视图,UIKit在NSIndexPath上声明了一个类别,使您能够获取表示的项索引(indexPath.item、indexPath.row)和节索引(indexPath.section),并从项和索引值构造新的索引路径对象。由于项位于其节中,因此通常必须先计算节索引号,然后才能通过其索引号标识项。配置集合视图对象时,请将代理对象分配给其代理属性。

UICollectionViewDelegate中提供的常用函数:
//询问委托选择指定的项是否可以被选择
-(BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath{
    return YES;
}

函数描述询问代理指定的单元格是否可以被选择。当用户试图在集合视图中选择单元格时,集合视图调用此方法;当以编程方式设置选择时,则不会调用此方法。如果不实现此方法,则默认返回值为YES。

参数 :

collectionView : 询问选择是否应更改的集合视图对象。

indexPath : 要选择的单元格的索引路径。

返回值 : 如果可以选择单元格,则为“YES”;如果不可以选择单元格,则为“NO”。

//选择单元格之后触发
-(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath{
}

函数描述通知代理已选择指定索引路径处的单元格。当用户在集合视图中成功选择单元格时,集合视图调用此方法;当以编程方式设置选择时,则不会调用此方法。

collectionView : 通知选择更改的集合视图对象。

indexPath : 选定单元格的索引路径。

//取消选择单元格之后触发
- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath{
}

函数描述通知代理指定路径处的单元格已被取消选择。当用户在集合视图中成功取消选择的单元格时,集合视图调用此方法;当以编程方式取消选择单元格时,则不会调用此方法。

参数 :

collectionView : 通知选择更改的集合视图对象。

indexPath : 取消选择的单元格的索引路径。

\color{red}{例如:创建一个可以多选的集合视图示例:}

//多选要设置属性allowsMultipleSelection为YES
-(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath{
    //获取当前要操作的Cell
    self.cell = (YSLSeeEvaluateCell *)[collectionView cellForItemAtIndexPath:indexPath];
    //可以对cell 的属性做一些设置
    self.cell.title.textColor = [YSLUiUtils colorPrimary];
    CALayer *layer = [self.cell.title layer];
    [layer setBorderWidth:0.5f];
    [layer setBorderColor:[YSLUiUtils colorPrimary].CGColor];
    layer.cornerRadius = 3.0f;
}

- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath{
    //获取当前要操作的Cell
    self.cell = (YSLSeeEvaluateCell *)[collectionView cellForItemAtIndexPath:indexPath];
    //可以对cell 的属性做一些设置
    self.cell.title.textColor = [YSLUiUtils colorThree];
    CALayer *layer = [self.cell.title layer];
    [layer setBorderWidth:0.5f];
    [layer setBorderColor:[YSLUiUtils colorSeven].CGColor];
    layer.cornerRadius = 3.0f;
}
- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath API_AVAILABLE(ios(8.0));

函数描述通知代理指定的单元格即将显示在集合视图中。集合视图在将单元格添加到其内容之前调用此方法。此方法检测的是单元格何时添加,而不是检测单元格何时出现。

参数 :

collectionView : 正在添加单元格的集合视图对象。

cell : 正在添加的单元格对象。

indexPath : 单元格表示的数据项的索引路径。

- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath;

函数描述通知代理指定的单元格已从集合视图中移除。此方法可以检测单元格何时从集合视图中移除,而不是检测单元格何时消失。

参数 :

collectionView : 移除单元格的集合视图对象。

cell : 已移除的单元格对象。

indexPath : 单元格表示的数据项的索引路径。

- (void)collectionView:(UICollectionView *)collectionView willDisplaySupplementaryView:(UICollectionReusableView *)view forElementKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath API_AVAILABLE(ios(8.0));

函数描述通知代理指定的补充视图即将显示在集合视图中。集合视图在向其内容添加补充视图之前调用此方法。此方法检测补充视图何时添加,而不是检测补充视图何时出现。

参数 :

collectionView : 正在添加补充视图的集合视图对象。

view : 正在添加的补充视图。

elementKind : 补充视图的类型。此字符串由显示视图的布局定义。

indexPath : 补充视图表示的数据项的索引路径。

- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingSupplementaryView:(UICollectionReusableView *)view forElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath;

函数描述通知代理指定的补充视图已从集合视图中移除。使用此方法可以检测何时从集合视图中移除补充视图,而不是检测补充视图何时消失。

参数 :

collectionView : 移除辅助视图的集合视图对象。

view : 已移除的补充视图。

elementKind : 补充视图的类型。此字符串由显示视图的布局定义。

indexPath : 补充视图表示的数据项的索引路径。

UICollectionViewFlowLayout - 流布局管理器

UICollectionViewFlowLayout是一种流布局管理器,即从左到右从上到下布局,每行包含尽可能多的单元格,单元格可以是相同大小或不同大小。以水平布局为例,单元格从左到右排列,但如果集合视图的高度允许显示更多的单元格,单元格也会从上到下尽可能多的排列,排列不下的才会滚动显示。

如图:这是一个1节中显示10个单元格的水平布局,但第一感觉似乎是垂直布局了,这和UICollectionView的高度有关系,因为水平布局时,还考虑到了上下布局的关系,所以想要UICollectionView一行水平方向展示单元格,最好钳制UICollectionView的高度。

截屏2022-08-12 09.52.20.png

流布局使用集合视图的代理对象(UICollectionViewDelegate)来确定每个节中单元格、页眉补充视图和页脚补充视图的大小,代理对象必须符合UICollectionViewDelegateFlowLayout协议(即通过集合视图delegate属性设置代理对象,但代理对象需要遵循UICollectionViewDelegateFlowLayout协议)。使用代理可以动态调整布局信息。例如需要使用代理对象为网格中的单元格指定不同的大小。如果不提供代理对象,则流布局使用使用此类属性设置的默认值。

流布局使用一个方向上的固定距离和另一个方向上的可滚动距离来布局其内容。例如在垂直滚动的网格状布局中,网格布局内容的宽度被限制为相应集合视图的宽度,而内容的高度则动态调整以匹配网格布局中要显示的节数和单元格数。默认情况下,布局配置为垂直滚动,但可以使用scrollDirection属性配置滚动方向

流布局中的每个节都可以有自己的自定义页眉补充视图和页脚补充视图。若要为视图配置页眉补充视图或页脚补充视图,必须将页眉补充视图或页脚补充视图的大小配置为非零(zero)值。可以通过实现适当的代理方法或为headerReferenceSize和footerReferenceSize属性分配适当的值来完成此操作。如果页眉或页脚大小为零(zero),则相应的视图不会添加到集合视图中。

UICollectionViewFlowLayout流布局管理器的一些常见属性
@property (nonatomic) CGFloat minimumLineSpacing;

属性描述 :网格状布局中,一节单元格之内,每行(或每列)单元格之间要使用的最小间距。如果代理对象未实现collectionView:layout:minimumLineSpacingForSectionAtIndex:方法,则流布局使用此属性中的值设置节中单元格的行(或列)间距。

对于垂直滚动的网格状布局,此值表示连续行之间的最小间距;对于水平滚动的网格状布局,此值表示连续列之间的最小间距。此间距不应用于页眉与第一行之间或最后一行与页脚之间的间距。此属性的默认值为10.0。

垂直布局时,minimumLineSpacing决定的行间距:

截屏2022-08-12 11.58.57.png

水平布局时,minimumLineSpacing决定的列间距:

截屏2022-08-12 11.47.43.png
@property (nonatomic) CGFloat minimumInteritemSpacing;

属性描述 :网格状布局中,一节单元格之内,要在同一行(或同一列)中的单元格与单元格之间使用的最小间距。如果委托对象未实现collectionView:layout:minimumitemSpacingForSectionAtIndex:方法,则流布局使用此属性中的值设置同一行(或同一列)中单元格与单元格之间的间距。

对于垂直滚动的网格状布局,此值表示同一行中单元格与单元格之间的最小间距;对于水平滚动网格状布局,此值表示同一列中单元格与单元格之间的最小间距。此间距用于计算一行可以容纳多少个项目,但在确定项目数之后,实际间距可能会向上调整。此属性的默认值为10.0。

垂直布局时,minimumInteritemSpacing决定的单元格与单元格间距:

截屏2022-08-12 13.59.52.png

水平布局时,minimumInteritemSpacing决定的单元格与单元格间距:

截屏2022-08-12 14.19.52.png

注意minimumLineSpacing属性与minimumInteritemSpacing属性间的配合与其默认值,例如数据需要水平一行展示时,设置单元格间的间距需要使用minimumLineSpacing而非minimumInteritemSpacing。

@property (nonatomic) CGSize itemSize;

属性描述用于单元格的默认大小。如果代理未实现collectionView:layout:sizeForItemAtIndexPath:方法,则流布局使用此属性中的值设置每个单元格的大小,这将导致所有单元格都具有相同的大小,默认大小值为CGSizeMake(50, 50)。

@property (nonatomic) CGSize estimatedItemSize API_AVAILABLE(ios(8.0));

属性描述集合视图中单元格的预估大小。当单元格动态调整其大小时,提供估计的单元格大小可以提高集合视图的性能,通过指定估计值,集合视图可以推迟确定其内容实际大小所需的某些计算。没有显示在屏幕上的单元格被假定为估计高度。

此属性的默认值为CGSizeZero。如果将其设置为任何其他值,则集合视图将使用单元格的preferredLayoutAttributesFittingAttributes:方法查询每个单元格的实际大小。如果所有单元格高度相同,请使用itemSize属性而不是此属性指定单元格大小

@property (nonatomic) UICollectionViewScrollDirection scrollDirection;

属性描述网格状布局的滚动方向。网格布局只沿着一个轴滚动,可以是水平方向,也可以是垂直方向。对于沿着轴水平方向滚动的网格布局,集合视图的高度用作内容的起始高度;对于沿着轴垂直方向滚动的网格布局,集合视图的宽度用作内容的起始宽度。这个属性的默认值是UICollectionViewScrollDirectionVertical。

  • UICollectionViewScrollDirection的枚举值 :
typedef NS_ENUM(NSInteger, UICollectionViewScrollDirection) {
    UICollectionViewScrollDirectionVertical, //垂直滚动
    UICollectionViewScrollDirectionHorizontal //水平滚动。
};
@property (nonatomic) CGSize headerReferenceSize;

属性描述用于节页眉补充视图的默认大小。如果代理未实现collectionView:layout:referenceSizeForHeaderInSection:方法,则流布局对象使用此属性中的值设置的默认的页眉补充视图大小。

在布局过程中,只使用与相应滚动方向相对应的大小。例如对于垂直滚动方向,布局对象使用属性返回的高度值(在这种情况下,页眉的宽度将设置为集合视图的宽度)。如果相应滚动维度中的大小为0,则不添加页眉补充视图,默认大小值为CGSizeMake(0, 0)。

@property (nonatomic) CGSize footerReferenceSize;

属性描述用于节页脚补充视图的默认大小。如果代理未实现collectionView:layout:referenceSizeForFooternSection:method,则流布局对象将使用为此属性中的值设置的默认页脚补充视图大小。

在布局过程中,只使用与相应滚动方向相对应的大小。例如对于垂直滚动方向,布局对象使用此属性指定的高度值(在这种情况下,页脚的宽度将设置为集合视图的宽度)。如果相应滚动维度中的大小为0,则不添加页脚补充视图,默认大小值为CGSizeMake(0, 0)。

@property (nonatomic) UIEdgeInsets sectionInset;

属性描述用于在每个节中布局单元格的内边距。sectionInset属性的类型是UIEdgeInsets结构体,UIEdgeInsets包括:top(上边界),left(左边界),bottom(下边界),right(右边界)4个成员。UIEdgeInsetsMake函数可以创建UIEdgeInsets结构体实例。如果代理对象未实现collectionView:layout:insetForSectionAtIndex:方法,则流布局使用此属性中的值设置每个节布局单元格的内边距。默认的边边距插入都设置为0。

截屏2022-08-12 16.53.08.png
@property (nonatomic) BOOL sectionHeadersPinToVisibleBounds API_AVAILABLE(ios(9.0));

属性描述一个布尔值,当此属性为“YES”时,设置悬停效果。节中的页眉补充视图将与内容一起滚动,滚动方向为垂直时,直到它们到达屏幕顶部,此时它们将固定到集合视图的上沿(即页眉补充视图悬停);滚动方向为水平时,直到它们到达屏幕左侧,此时它们将固定到集合视图的左沿(即页眉补充视图悬停)。每个滚动到屏幕顶部的新页眉补充视图都会将先前固定的页眉补充视图推离屏幕。此属性的默认值为“NO”。

@property (nonatomic) BOOL sectionFootersPinToVisibleBounds API_AVAILABLE(ios(9.0));

属性描述一个布尔值,当此属性为“YES”时,设置悬停效果。节中的页脚视图补充视图将随内容一起滚动,滚动方向为垂直时,直到它们到达屏幕底部,此时它们将固定到集合视图的下沿(即页脚补充视图悬停);滚动方向为水平时,直到它们到达屏幕右侧,此时它们将固定到集合视图的右沿(即页眉补充视图悬停)。滚动到屏幕底部的每个新页脚补充视图都会将先前固定的页脚补充视图推离屏幕。此属性的默认值为“NO”。

UICollectionViewDelegateFlowLayout提供的一些方法
//动态设置每个Item的尺寸大小
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath{
}

函数描述询问代理指定单元格的大小。如果不实现此方法,则流布局将使用其itemSize属性中的值来设置单元格的大小。此方法的实现可以返回一组固定的大小或根据单元格的内容动态调整大小。

流布局不会裁剪单元格边界以使其适合网格布局边界,因此返回的值必须允许单元格在集合视图中完全显示。例如在垂直滚动的网格状布局中,单个单元格的宽度不能超过集合视图本身的宽度(减去任何节插入)。但是在滚动方向上,单元格可以大于集合视图,因为剩余的内容始终可以滚动到视图中。

参数 :

collectionView : 显示流布局的集合视图对象。

collectionViewLayout : 请求信息的布局对象。

indexPath :单元格的索引路径。

返回值 : 指定单元格的宽度和高度。两个值都必须大于0。

//动态设置每个分区的EdgeInsets
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section{
}

函数描述要求代理将内边距应用于指定节中的内容。如果不实现此方法,则流布局将使用其sectionInset属性中的值来设置内边距。此方法的实现可以返回一组固定的边距大小,或者为每个部分返回不同的边距大小。

节插入是仅应用于节中内容的边距,它们表示页眉补充视图和第一行单元格之间以及最后一行单元格和页脚补充视图之间的距离,它们还指示一行单元格两边的间距,它们不会影响页眉补充视图或页脚补充视图本身的大小。

参数 :

collectionView : 显示流布局的集合视图对象。

collectionViewLayout : 请求信息的布局对象。

section : 需要插入的节的索引号。

返回值 : 应用于节中内容的内边距。

//动态设置每行的间距大小
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section{
}

函数描述询问代理指定节中连续行或列之间的间距。如果未实现此方法,则流布局将使用其minimumLineSpacing属性中的值来设置行或列之间的间距。此方法的实现可以为每个节中连续行或列之间返回固定间距值或不同的间距值。

对于垂直滚动网格,此值表示连续行之间的最小间距。对于水平滚动网格,此值表示连续列之间的最小间距。此间距不应用于页眉与第一行之间或最后一行与页脚之间的间距。

参数 :

collectionView : 显示流布局的集合视图对象。

collectionViewLayout : 请求信息的布局对象。

section : 需要行或列间距的节的索引号。

返回值 : 分段中连续线之间的最小空间(以点为单位)。

//动态设置每个单元格的间距大小
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section{
}

函数描述要求代理指定节的行或列中连续单元格之间的间距。如果未实现此方法,则流布局将使用其minimumitemSpacing属性中的值来设置单元格与单元格之间的间距。此方法的实现可以为每个节的单元格与单元格之间返回固定间距值或不同的间距值。

对于垂直滚动网格,此值表示同一行中单元格与单元格之间的最小间距。对于水平滚动网格,此值表示同一列中单元格与单元格之间的最小间距。此间距用于计算一行可以容纳多少个单元格,但在确定单元格数之后,实际间距可能会向上调整。

参数 :

collectionView : 显示流布局的集合视图对象。

collectionViewLayout : 请求信息的布局对象。

section : 需要单元格与单元格间距的节的索引号。

返回值 : 分段线中连续单元格之间的最小间距(以点为单位)。

//动态设置某个分区头视图大小
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section{
}

函数描述询问代理指定节中页眉补充视图的大小。如果不实现此方法,则流布局将使用其headerReferenceSize属性中的值设置页眉充视视图的大小。在布局过程中,只使用与相应滚动方向相对应的大小,例如对于垂直滚动方向,布局对象使用方法返回的高度值(在这种情况下,页眉的宽度将设置为集合视图的宽度)。如果相应滚动维度中的大小为0,则不添加页眉补充视图。

参数:

collectionView : 显示流布局的集合视图对象。

collectionViewLayout : 请求信息的布局对象。

section : 正在请求其页眉补充视图大小的节的索引。

返回值 : 页眉补充视图的大小。如果返回的值为CGSizeMake(0, 0),则不添加页眉补充视图。

//动态设置某个分区尾视图大小
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section{
}

函数描述询问代理指定节中页脚补充视图的大小。如果不实现此方法,则流布局将使用其footerReferenceSize属性中的值来设置页脚补充视图的大小。在布局过程中,只使用与相应滚动方向相对应的大小,例如对于垂直滚动方向,布局对象使用此属性指定的高度值(在这种情况下,页脚的宽度将设置为集合视图的宽度)。如果相应滚动维度中的大小为0,则不添加页脚。

参数 :

collectionView : 显示流布局的集合视图对象。

collectionViewLayout : 请求信息的布局对象。

section : 正在请求其页脚补充视图大小的节的索引。

返回值 : 页脚补充视图的大小。如果返回的值为CGSizeMake(0, 0),则不添加页脚补充视图。

UICollectionViewLayout - 布局信息的抽象基类

用于为集合视图生成布局信息的抽象基类。布局对象的工作是确定单元格、补充视图和装饰视图在集合视图范围内的位置,并在需要时向集合视图报告该信息。然后集合视图将提供的布局信息应用于相应的视图,以便在屏幕上显示它们。

必须子类化UICollectionViewLayout才能使用它,不过在考虑子类化之前,应该查看UICollectionViewFlowLayout类,看看它是否能够适应布局的需求。

UICollectionViewLayout常用属性
@property (nullable, nonatomic, readonly) UICollectionView *collectionView;

属性描述当前使用此布局对象的集合视图对象

UICollectionViewLayout常用函数
- (instancetype)init NS_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER;

函数描述 :初始化相关函数。

- (void)invalidateLayout;

函数描述使当前布局失效并触发布局更新。可以随时调用此方法来更新布局信息。此方法使集合视图本身的布局无效,并立即返回。因此,可以从同一代码块多次调用此方法,而无需触发多个布局更新。实际布局更新发生在下一个视图布局更新周期中。如果重写此方法,则必须在实现中的某个点调用super

\color{red}{例如更新集合视图布局的代码片段 } invalidateLayout使用示例

//获取集合视图布局
NHAlignmentFlowLayout *layout = (NHAlignmentFlowLayout *)self.collectionView.collectionViewLayout;
//判断是否显示页面边距与商品边距
BOOL hasBorder = (dataModel.style_border == 0) ? YES : NO;
if (!hasBorder) {
    //不页面边距与商品边距
   self.style_goods_item_margins = 0.0;
   self.style_goods_page_margins = 0.0;
}
//行间距
layout.minimumLineSpacing = self.style_goods_item_margins;
//列间距
layout.minimumInteritemSpacing = self.style_goods_item_margins;
//cell大小
layout.itemSize = [(NSValue *)dataModel.extraInfo CGSizeValue];
//禁用动画转换视图
[UIView performWithoutAnimation:^{
    //刷新集合视图
    [self.collectionView reloadData];
    //立即触发集合视图更新布局
    [self.collectionView.collectionViewLayout invalidateLayout];
    //设置集合视图内容内间距
    self.collectionView.contentInset = UIEdgeInsetsMake(self.style_goods_page_margins
                                                                , self.style_goods_page_margins
                                                                , self.style_goods_page_margins
                                                                , self.style_goods_page_margins);
}];

UICollectionViewLayout - (UISubclassingHooks)分类

UICollectionViewLayout (UISubclassingHooks)常用属性
@property(nonatomic, readonly) CGSize collectionViewContentSize;

属性描述返回集合视图内容的宽度和高度。子类必须覆盖此属性并使用它来返回集合视图内容的宽度和高度,这些值表示所有内容的宽度和高度,而不仅仅是当前可见的内容。 集合视图使用此信息来配置其自己的内容大小以用于滚动目的,此属性默认值CGSizeZero。

\color{red}{例如获取集合视图内容的高度代码片段 } collectionViewContentSize使用示例

- (void)setShopActivity:(NSArray<YSCResponseModelShopStreetShopActivityModel> *)shopActivity{
    _shopActivity = shopActivity;
    [self setNeedsLayout];
    [self layoutIfNeeded];
    [self.activityCollectionView reloadData];
    CGFloat activityCollectionViewHeoght = self.activityCollectionView.collectionViewLayout.collectionViewContentSize.height;
    [self.activityCollectionView mas_updateConstraints:^(MASConstraintMaker *make) {
        make.height.mas_equalTo(activityCollectionViewHeoght);
    }];
}
UICollectionViewLayout (UISubclassingHooks)常用函数
- (void)prepareLayout;

函数描述集合视图第一次显示其内容时,以及布局因视图更改而显式或隐式无效时,集合视图首先调用这个方法,让布局对象有机会为即将到来的布局操作做准备。此方法的默认实现什么也不做。 子类可以覆盖它并使用它来设置数据结构或执行稍后执行布局之前所需的任何初始计算。

- (nullable NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect; 

函数描述返回指定矩形中所有单元格和视图的布局属性。子类必须覆盖此方法并使用它来返指定矩形中的所有可视元素的布局信息,这些可视元素包括单元格、补充视图和装饰视图。

在创建布局属性时,始终要创建一个UICollectionViewLayoutAttributes对象,该对象表示正确的元素类型(单元格、补充元素或装饰元素)。集合视图区分每种类型的属性,并使用这些信息来决定创建哪些视图以及如何管理它们。

参数 :

rect : 包含可视元素的矩形(在集合视图的坐标系统中指定)。

返回值 :UICollectionViewLayoutAttributes对象的数组,表示单元格、补充视图和装饰视图的布局信息。默认实现返回nil。

- (nullable UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath;

函数描述返回在指定索引路径上的单元格布局属性。子类必须重写此方法,并使用它返回集合视图中单元格的布局信息。使用此方法只能为相应的单元格提供布局信息,不要将其用于补充视图或装饰视图。

参数 :

indexPath : 单元格的索引路径。

返回值 : 包含要应用于单元格的布局属性对象。

UICollectionViewLayoutAttributes - 布局属性对象

管理集合视图中给定可视元素的布局相关属性的布局对象。当集合视图要求布局对象创建该类的实例时会创建该类的实例。然后集合视图使用布局信息在其边界内定位单元格和补充视图。

UICollectionViewLayoutAttributes常用属性
@property (nonatomic) CGRect frame;

属性描述可视元素的框架矩形。框架矩形以点为单位测量,并在集合视图的坐标系中指定,设置此属性的值还会设置center和size属性的值。

@property (nonatomic) CGPoint center;

属性描述可视元素的中心点。中心点在集合视图的坐标系中指定,设置此属性的值也会更新frame属性中矩形的原点。

@property (nonatomic) CGSize size;

属性描述 : 可视元素的大小。设置此属性的值还会更改frame和bounds属性返回的矩形的大小。

@property (nonatomic) CGRect bounds API_AVAILABLE(ios(7.0));

属性描述可视元素的的边界。设置边界时,边界矩形的原点必须始终位于 (0, 0)。 更改边界矩形也会更改size属性中的值以匹配新的边界大小。

@property (nonatomic) CGFloat alpha;

属性描述可视元素的透明度。可能的值介于0.0��(透明)和1.0(不透明)之间, 默认值为 1.0。

@property (nonatomic) NSInteger zIndex;

属性描述指定可视元素在z轴上的位置。此属性用于确定布局期间可视元素的从前到后的顺序, 具有较高索引值的可视元素出现在具有较低值的可视元素之上,具有相同值的项目具有未确定的顺序,此属性的默认值为 0。

@property (nonatomic, getter=isHidden) BOOL hidden;

属性描述 :确定可视元素当前是否显示。此属性的默认值为 NO, 作为优化,如果此属性设置为YES,则集合视图可能不会创建相应的可视元素。

@property (nonatomic, strong) NSIndexPath *indexPath;

属性描述集合视图中可视元素(单元格、页眉补充视图、页脚补充视图)的索引路径。索引路径包含节(section)的索引和该节内的项(item或row)的索引,这两个唯一值标识了集合视图中相应可视元素的位置。

@property (nonatomic, readonly) UICollectionElementCategory representedElementCategory;

属性描述可视元素的类型。可以使用此属性中的值来区分布局属性是用于单元格、补充视图还是装饰视图。

  • UICollectionElementCategory枚举
typedef NS_ENUM(NSUInteger, UICollectionElementCategory) {
    //单元格。
    UICollectionElementCategoryCell,
    //补充视图
    UICollectionElementCategorySupplementaryView,
    //装饰视图
    UICollectionElementCategoryDecorationView
};
@property (nonatomic, readonly, nullable) NSString *representedElementKind; 

属性描述目标视图的特定于布局的标识符。可以使用此属性中的值来标识与布局属性关联的补充或装饰视图的特定用途,如果表示的ElementCategory属性包含值UICollectionElementCategoryCell,则此属性为nil。

瀑布流布局的简单实现

本来作为一个小白级别的UICollectionView使用者,我是很开心的,直到有一天项目经理找了一张图甩在我的脸上,并温和的说,当单元格内容多的时候,他想要的布局是这样的:

winfred_zen的博客.png

我觉得差不多就得了,但是产品经理想要他觉得,不想要我觉得,所以我只能再次发挥面向百度编程的本领,发现这是流布局的一种 - 瀑布流布局,网上有很多的实现方法,而我只想说,搞这么多样式的布局干嘛,都是做程序员的,你们别再卷了,我已经学不动了,放弃那些奇奇怪怪的布局吧。但是代码该写还是要写,谁让人家是经理呢!!

//
//  TSChannelFourViewController.m

#import "TSChannelFourViewController.h"

/**
 模型
 */
@interface CategoryItemObject : NSObject

@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSArray<NSString *> *categoryArray;

@end

@implementation CategoryItemObject

- (instancetype)init {
    self = [super init];
    if (self) {
        self.title = @"分类名称";
        self.categoryArray = @[@"《清明上河图》描绘的是清明时节北宋都城汴京(今河南开封)东角子门内外和汴河两岸的繁华热闹景象,再现了12世纪北宋全盛时期都城汴京的生活面貌。",
            @"《千里江山图》卷是北宋画家王希孟传世的唯一作品。此图描绘了祖园的锦绣河山,一向被视为宋代青绿山水中的巨制杰构。",
            @"《步辇图》画幅描绘的是唐太宗李世民在宫内接见松赞干布派来的吐蕃使臣禄东赞的情景,全画线条纯熟,设色浓重、鲜艳,是一幅出色的工笔重彩人物画作品。",
            @"《平复帖》的书写年代距今己有1700余年,是现存年代最早并真实可信的西晋名家法帖,在中国书法史上占有重要地位。",
            @"长信宫灯,西汉的一件铜鎏金青铜器,大约制成于公元前151年。这盏灯于1968年在中山蜻王刘胜之妻窦绾墓出土,灯身上刻有“长信”的铭文,被誉为“中华第一灯“。",
            @"越王勾践剑,1965年12月出土于湖北江陵望山一号楚墓,出土时插在漆木剑鞘里,出鞘时仍然寒光闪闪,耀人眼目,被誉为“天下第一剑”。",
            @"错金博山炉,这是西汉村作为香薰、薰炉用的青铜器,因为造型象征的是传说中的海上仙山一博山:所以叫做博山炉。整体精致华美,被称为“史上最豪华的香薰”。",
            @"曾侯乙编钟,1978年出土于湖北随州曾候乙墓,架长7.48米、高2.65米,全套编钟共六十五件,能演奏五声、六声或七声音阶的乐曲。",
            @"曾侯乙尊盘,1978年出土于湖北随州市曾侯乙墓,由尊和盘两件器物组成,商周青铜器的巅峰之作。",
            ];
    }
    return self;
}

@end

/**
 布局
 */
@interface UICollectionViewWaterFallLayout : UICollectionViewFlowLayout

@end

@interface UICollectionViewWaterFallLayout()

//一行单元格计数
@property (nonatomic, assign) NSInteger oneRowCountCell;
//存放节中第一行Y轴最小的单元格布局对象字典
@property (nonatomic, strong) NSMutableDictionary<NSString *,UICollectionViewLayoutAttributes *> *oneRowminYCellAttributesDic;
//存放已经更改布局的布局对象字典
@property (nonatomic, strong) NSMutableDictionary<NSString *,UICollectionViewLayoutAttributes *> *layoutedAttributesDic;

@end


@implementation UICollectionViewWaterFallLayout

/// 准备布局
- (void)prepareLayout {
    if (self.scrollDirection == UICollectionViewScrollDirectionVertical) {
        //集合视图可以显示内容的宽度
        CGFloat collectionContentWidth = self.collectionView.frame.size.width - self.sectionInset.left - self.sectionInset.right;
        //单元格加单元格间距的宽度
        CGFloat cellAndItemSpacingwidth = 0;
        //构造用于检索的框架矩形
        CGSize size = super.collectionViewContentSize;
        CGRect rect = CGRectMake(self.collectionView.frame.origin.x, self.collectionView.frame.origin.y, size.width, size.height);
        //检索构造的框架矩形中所有可视元素的布局属性
        NSArray *originalAttributes = [super layoutAttributesForElementsInRect:rect];
        //遍历构造的框架矩形中所有可视元素的布局属性
        for (UICollectionViewLayoutAttributes *attributes in originalAttributes) {
            //如果可视元素为单元格
            if (attributes.representedElementCategory == UICollectionElementCategoryCell) {
                //累加一个单元格的宽度
                cellAndItemSpacingwidth += attributes.size.width;
                //如果单元格加单元格间距的宽度小于等于集合视图显示内容的宽度
                if (cellAndItemSpacingwidth <= collectionContentWidth) {
                    //一行单元格计数增加1
                    self.oneRowCountCell ++;
                    //累加一个单元格间距
                    cellAndItemSpacingwidth += self.minimumInteritemSpacing;
                } else {
                    break;
                }
            }
        }
        //强制一行单元格计数最小为1
        if (self.oneRowCountCell <= 0) {
            self.oneRowCountCell = 1;
        }
    }
}

/// 返回指定矩形中所有单元格和视图的布局属性
- (nullable NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {
    if (self.scrollDirection == UICollectionViewScrollDirectionVertical) {
        //返回指定矩形中所有单元格和视图的布局属性
        NSArray *originalAttributes = [super layoutAttributesForElementsInRect:rect];
        NSMutableArray *updatedAttributes = [NSMutableArray arrayWithArray:originalAttributes];
        //遍历构造的框架矩形中所有可视元素的布局属性
        for (UICollectionViewLayoutAttributes *attributes in originalAttributes) {
            //如果可视元素为单元格
            if (attributes.representedElementCategory == UICollectionElementCategoryCell) {
                //如果单元格的项索引小于一行单元格计数(说明是第一行单元格)
                if (attributes.indexPath.item < self.oneRowCountCell) {
                    //获取节中Y轴最小的单元格布局对象
                    UICollectionViewLayoutAttributes *minYCellAttributes = [self.oneRowminYCellAttributesDic objectForKey:[NSString stringWithFormat:@"%ld",(long)attributes.indexPath.section]];
                    //如果获取的单元格布局对象为nil
                    if (minYCellAttributes == nil) {
                        //将这个单元格布局对象作为节中Y轴最小的单元格存入字典
                        [self.oneRowminYCellAttributesDic setValue:attributes forKey:[NSString stringWithFormat:@"%ld",(long)attributes.indexPath.section]];
                    } else {
                        //如果在字典中获取了单元格布局对象,将当前这个单元格布局对象与字典中获取的进行最小Y轴比较
                        //利用字典Key相同的值会进行覆盖,保证存储的是第一行单元格中Y轴以最小的单元格布局对象
                        if (CGRectGetMinY(attributes.frame) < CGRectGetMinY(minYCellAttributes.frame)) {
                            [self.oneRowminYCellAttributesDic setValue:attributes forKey:[NSString stringWithFormat:@"%ld",(long)attributes.indexPath.section]];
                        }
                    }
                }
            }
        }
        //再次遍历构造的框架矩形中所有可视元素的布局属性
        for (UICollectionViewLayoutAttributes *attributes in originalAttributes) {
            //如果可视元素为单元格
            if (attributes.representedElementCategory == UICollectionElementCategoryCell) {
                //获取单元格布局属性对象在数组中的索引
                NSUInteger index = [updatedAttributes indexOfObject:attributes];
                //返回位于指定索引路径的单元格的布局信息
                UICollectionViewLayoutAttributes *cellLayoutAttributes = [self layoutAttributesForItemAtIndexPath:attributes.indexPath];
                //如果单元格布局对象的项索引小于一行单元格计数(说明是第一行单元格)
                if (cellLayoutAttributes.indexPath.item < self.oneRowCountCell) {
                    //获取一节中第一行Y轴最小的单元格布局对象
                    UICollectionViewLayoutAttributes *minYCellAttributes = [self.oneRowminYCellAttributesDic objectForKey:[NSString stringWithFormat:@"%ld",(long)cellLayoutAttributes.indexPath.section]];
                    //将位于节中第一行的单元格布局对象的Y轴都设置为最小的那个
                    cellLayoutAttributes.frame = CGRectMake(cellLayoutAttributes.frame.origin.x, minYCellAttributes.frame.origin.y, cellLayoutAttributes.frame.size.width, cellLayoutAttributes.frame.size.height);
                    //将更改过布局属性之的布局属性对象存放到已经更改布局的布局对象字典中,以section-item为Key
                    [self.layoutedAttributesDic setValue:cellLayoutAttributes forKey:[NSString stringWithFormat:@"%ld-%ld",(long)cellLayoutAttributes.indexPath.section,(long)cellLayoutAttributes.indexPath.item]];
                } else {
                    //单元格布局对象的项索引大于或等于一行单元格计数(说明不是第一行单元格)
                    //使用当前遍历出的单元格布局属性对象的项索引减去一行单元格计数,获取当前遍历出的单元格布局属性在垂直布局中对应的它上面显示的那个单元格布局属性索引
                    //例如项索引为3,一行单元格计数为3,结果为0;项索引为4,一行单元格计数为3,结果为1;项索引为5,一行单元格计数为3,结果为2;这就可以计算出当前遍历出的单元格布局属性在垂直布局中对应的它上面的那个单元格布局属性索引
                    NSInteger item = cellLayoutAttributes.indexPath.item - self.oneRowCountCell;
                    //在已经更改布局的布局对象字典获取当前遍历出的单元格布局属性在垂直布局中对应的它上面显示的那个单元格布局属性
                    UICollectionViewLayoutAttributes *layoutedAttributes = [self.layoutedAttributesDic objectForKey:[NSString stringWithFormat:@"%ld-%ld",(long)cellLayoutAttributes.indexPath.section,(long)item]];
                    //设置当前遍历出的单元格布局属性的Y轴值为它上面显示的那个单元格布局属性最大的Y轴值加上列间距
                    cellLayoutAttributes.frame = CGRectMake(cellLayoutAttributes.frame.origin.x, CGRectGetMaxY(layoutedAttributes.frame) + self.minimumLineSpacing, cellLayoutAttributes.frame.size.width, cellLayoutAttributes.frame.size.height);
                    //将当前遍历出的单元格布局属性也存入存放已经更改布局的布局对象字典中
                    [self.layoutedAttributesDic setValue:cellLayoutAttributes forKey:[NSString stringWithFormat:@"%ld-%ld",(long)cellLayoutAttributes.indexPath.section,(long)cellLayoutAttributes.indexPath.item]];
                }
                //设置修改后的当前遍历出的单元格布局属性
                updatedAttributes[index] = cellLayoutAttributes;
            }
        }
        return updatedAttributes;
    } else {
        return [super layoutAttributesForElementsInRect:rect];
    }
}

- (NSMutableDictionary<NSString *,UICollectionViewLayoutAttributes *> *)oneRowminYCellAttributesDic {
    if (_oneRowminYCellAttributesDic == nil) {
        _oneRowminYCellAttributesDic = [[NSMutableDictionary alloc]init];
    }
    return _oneRowminYCellAttributesDic;
}

- (NSMutableDictionary<NSString *,UICollectionViewLayoutAttributes *> *)layoutedAttributesDic {
    if (_layoutedAttributesDic == nil) {
        _layoutedAttributesDic = [[NSMutableDictionary alloc]init];
    }
    return _layoutedAttributesDic;
}

@end

/**
 页眉补充视图
 */
UIKIT_EXTERN NSString *const HeaderViewReuseIdentifier;
@interface HeaderView : UICollectionReusableView

@property (nonatomic, strong) UILabel *titleLabel;

@end

NSString *const HeaderViewReuseIdentifier = @"HeaderViewReuseIdentifier";
@implementation HeaderView

- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        self.titleLabel = [[UILabel alloc]initWithFrame:self.bounds];
        self.titleLabel.font = [UIFont systemFontOfSize:18];
        self.titleLabel.textColor = [UIColor blackColor];
        self.titleLabel.textAlignment = NSTextAlignmentCenter;
        self.titleLabel.numberOfLines = 0;
        [self addSubview:self.titleLabel];
    }
    return self;
}

@end

/**
 单元格
 */
UIKIT_EXTERN NSString *const CategoryCellReuseIdentifier;
@interface CategoryCell : UICollectionViewCell

@property (nonatomic, strong) UILabel *categoryNameLabel;

@end

NSString *const CategoryCellReuseIdentifier = @"CategoryCellReuseIdentifier";
@implementation CategoryCell

- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        self.layer.cornerRadius = 8.0;
        self.layer.borderWidth = 1.0;
        self.categoryNameLabel = [[UILabel alloc]initWithFrame:CGRectZero];
        self.categoryNameLabel.font = [UIFont systemFontOfSize:15];
        self.categoryNameLabel.textAlignment = NSTextAlignmentCenter;
        [self.contentView addSubview:self.categoryNameLabel];
        self.categoryNameLabel.numberOfLines = 0;
        self.categoryNameLabel.translatesAutoresizingMaskIntoConstraints = NO;
        [NSLayoutConstraint activateConstraints:@[
            [self.categoryNameLabel.topAnchor constraintEqualToAnchor:self.topAnchor constant:5],
            [self.categoryNameLabel.leadingAnchor constraintEqualToAnchor:self.leadingAnchor constant:5],
            [self.categoryNameLabel.trailingAnchor constraintEqualToAnchor:self.trailingAnchor constant:-5],
            [self.categoryNameLabel.bottomAnchor  constraintEqualToAnchor:self.bottomAnchor constant:-5],
        ]];
    }
    return self;
}

@end

/**
 控制器
 */
@interface TSChannelFourViewController ()<UICollectionViewDataSource,UICollectionViewDelegate>

@property (nonatomic, strong) UICollectionView *collectionView;
@property (nonatomic, strong) NSMutableArray<CategoryItemObject *> *dataSource;

@end

@implementation TSChannelFourViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.extendedLayoutIncludesOpaqueBars = YES;
    //流布局
    UICollectionViewWaterFallLayout *flowLayout = [[UICollectionViewWaterFallLayout alloc]init];
    flowLayout.scrollDirection = UICollectionViewScrollDirectionVertical;
    flowLayout.minimumLineSpacing = 15;
    flowLayout.minimumInteritemSpacing = 15;
    flowLayout.sectionInset = UIEdgeInsetsMake(15, 15, 15, 15);
    flowLayout.headerReferenceSize = CGSizeMake(45, 45);
    //集合视图
    self.collectionView = [[UICollectionView alloc]initWithFrame:CGRectZero collectionViewLayout:flowLayout];
    self.collectionView.dataSource = self;
    self.collectionView.delegate = self;
    self.collectionView.allowsMultipleSelection = YES;
    self.collectionView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
    [self.collectionView registerClass:CategoryCell.class forCellWithReuseIdentifier:CategoryCellReuseIdentifier];
    [self.collectionView registerClass:HeaderView.class forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:HeaderViewReuseIdentifier];
    [self.view addSubview:self.collectionView];
    self.collectionView.translatesAutoresizingMaskIntoConstraints = NO;
    [NSLayoutConstraint activateConstraints:@[
        [self.collectionView.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor],
        [self.collectionView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
        [self.collectionView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
        [self.collectionView.bottomAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor],
    ]];
}

/// 数据源
- (NSMutableArray<CategoryItemObject *> *)dataSource {
    if (_dataSource == nil) {
        _dataSource = [[NSMutableArray alloc]init];
        for (int i = 0; i < 3; i++) {
            CategoryItemObject *categoryItem = [[CategoryItemObject alloc]init];
            [_dataSource addObject:categoryItem];
        }
    }
    return _dataSource;
}

#pragma mark - UICollectionViewDataSource
//有几节单元格
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
    return self.dataSource.count;
}

//每节有多少个单元格
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
    CategoryItemObject *categoryItem = self.dataSource[section];
    return categoryItem.categoryArray.count;
}

//配置单元格
- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    NSString *categoryName = self.dataSource[indexPath.section].categoryArray[indexPath.row];
    CategoryCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:CategoryCellReuseIdentifier forIndexPath:indexPath];
    cell.categoryNameLabel.text = categoryName;
    //设置选中与未选中时单元格的样式
    if (cell.isSelected) {
        cell.backgroundColor = [UIColor colorWithRed:226 / 255.0f green:240 / 255.0f blue:253 / 255.0f alpha:1.0];
        cell.layer.borderColor = [UIColor blueColor].CGColor;
        cell.categoryNameLabel.textColor = [UIColor blueColor];
    } else {
        cell.backgroundColor = [UIColor whiteColor];
        cell.layer.borderColor = [UIColor blackColor].CGColor;
        cell.categoryNameLabel.textColor = [UIColor blackColor];
    }
    return cell;
}

//配置页眉补充视图
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {
    if ([kind isEqualToString:UICollectionElementKindSectionHeader]) {
        CategoryItemObject *categoryItem = self.dataSource[indexPath.section];
        HeaderView *headerView = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:HeaderViewReuseIdentifier forIndexPath:indexPath];
        headerView.titleLabel.text = categoryItem.title;
        return headerView;
    }
    return nil;
}

#pragma mark - UICollectionViewDelegate
//单元格已被选择
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
    CategoryCell *cell = (CategoryCell *)[collectionView cellForItemAtIndexPath:indexPath];
    cell.backgroundColor = [UIColor colorWithRed:226 / 255.0f green:240 / 255.0f blue:253 / 255.0f alpha:1.0];
    cell.layer.borderColor = [UIColor blueColor].CGColor;
    cell.categoryNameLabel.textColor = [UIColor blueColor];
}

//单元格已被取消选择
- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(NSIndexPath *)indexPath{
    CategoryCell *cell = (CategoryCell *)[collectionView cellForItemAtIndexPath:indexPath];
    cell.backgroundColor = [UIColor whiteColor];
    cell.layer.borderColor = [UIColor blackColor].CGColor;
    cell.categoryNameLabel.textColor = [UIColor blackColor];
}

#pragma mark - UICollectionViewDelegateFlowLayout
//每个单元格的大小
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath{
    //计算cell宽度
    UICollectionViewFlowLayout *flowLayout = (UICollectionViewFlowLayout *)collectionView.collectionViewLayout;
    CGFloat cellWidth = (collectionView.bounds.size.width - flowLayout.minimumInteritemSpacing * 2 - flowLayout.sectionInset.left - flowLayout.sectionInset.right) / 3;
    //计算cell高度
    NSString *categoryName = self.dataSource[indexPath.section].categoryArray[indexPath.row];
    CGFloat cellHeight = [categoryName boundingRectWithSize:CGSizeMake(floor(cellWidth) - 10, CGFLOAT_MAX) options:NSStringDrawingUsesFontLeading | NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName : [UIFont systemFontOfSize:15]} context:nil].size.height;
    return CGSizeMake(floor(cellWidth), ceil(cellHeight) + 10 );
}

@end

注:示例中控制器是没有导航栏的,如果是拥有导航栏的视图控制器,需要注意控制器extendedLayoutIncludesOpaqueBars属性与集合视图contentInsetAdjustmentBehavior属性的设置,否则布局容易错乱。

效果如图 :

截屏2022-08-19 17.46.25.png

UICollectionView高度自适应

estimatedItemSize的推出,可以让CollectionView中也能让 cell 自适应内容大小,达到自动适应高度的预期效果

UICollectionView的高度自适应的原理:

    1. CollectionView根据 layout 的 estimatedItemSize 算出估计的 contentSize,有了 contentSize CollectionView就开始显示
    1. CollectionView 在显示的过程中,即将被显示的 cell 根据 autolayout 的约束算出自适应内容的 size
    1. layout 从 CollectionView 里获取更新过的 size attribute
    1. layout 返回最终的 size attribute 给 CollectionView。CollectionView 使用这个最终的 size attribute 展示 cell

问题记录

1.关于集合视图使用Masonry设置约束,设置内容水平布局,第一次reloadData时cell不显示的问题:

Jietu20210405-222413.gif

设置UICollectionView的代码如下:

    ///合并的分类集合视图
    //初始化合并的分类集合视图布局
    UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc]init];
    //设置水平滚动
    layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
    //初始化合并的分类集合视图
    _mergeStyleCollectionView = [[UICollectionView alloc]initWithFrame:CGRectZero collectionViewLayout:layout];
    //设置合并的分类集合视图背景色
    _mergeStyleCollectionView.backgroundColor = [UIColor whiteColor];
    //设置合并的分类集合视图内容内边距
    _mergeStyleCollectionView.contentInset = UIEdgeInsetsMake(0, 10, 0, 0);
    //设置合并的分类集合视图数据源
    _mergeStyleCollectionView.dataSource = self;
    //设置合并的分类集合视图代理
    _mergeStyleCollectionView.delegate = self;
    //设置合并的分类集合视图不显示垂直滚动条
    _mergeStyleCollectionView.showsVerticalScrollIndicator = NO;
    //设置合并的分类集合视图不显示水平滚动条
    _mergeStyleCollectionView.showsHorizontalScrollIndicator = NO;
    //添加合并的分类集合视图
    [self addSubview:_mergeStyleCollectionView];
    //设置合并的分类集合视图约束
    [_mergeStyleCollectionView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(self);
        make.top.bottom.equalTo(self);
        make.right.equalTo(self.mas_right).offset(- 28);
    }];
    //注册商品子分类cell
    [_mergeStyleCollectionView registerClass:[YSCGoodsSubCategoryItem class] forCellWithReuseIdentifier:YSC_GOODSSUBCATEGORY_ITEM];

问题出在UICollectionView设置约束的方式上,猜测可能是因为Masonry设置约束的方式上,UICollectionView在水平方向布局时,可能需要确切的高度,但使用Masonry时,第一次reloadData时,没有办法拿到UICollectionView的确切高度,导致cell无法显示,将约束改为如下代码:

//设置合并的分类集合视图约束
 [_mergeStyleCollectionView mas_makeConstraints:^(MASConstraintMaker *make) {
     make.left.equalTo(self);
     make.top.equalTo(self);
     make.right.equalTo(self.mas_right).offset(- 28);
     make.height.mas_equalTo(45);
  }];

UICollectionView的cell在第一次reloadData时能够正常显示:

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

推荐阅读更多精彩内容