IOS - customize the layout of CollectionView and add headers

Keywords: Attribute less

  • Look at the examples first, then the knowledge points

    Formed collectionView
  • In viewDidLoad
- (void)viewDidLoad {
    [super viewDidLoad];
    //Here, the data obtained locally is stored in the array self.Array
    NSArray * shopsArray = [shopModel mj_objectArrayWithFilename:@"1.plist"];
    [self.Array addObjectsFromArray:shopsArray];
    //This is a custom waterfall flow
    FlowLayout *flowlayout1 = [[FlowLayout alloc] init];
    //In order to be able to scale the image in the array according to the aspect ratio
    flowlayout1.delegate = self;
    self.mainView = [[UICollectionView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height) collectionViewLayout:flowlayout1];
    self.mainView.backgroundColor = [UIColor redColor];
    self.mainView.delegate = self;
    self.mainView.dataSource = self;
    self.mainView.scrollEnabled = YES;
    [self.view addSubview:self.mainView];
    //Sign up for a Cell
    [self.mainView registerClass:[CollectionCell class] forCellWithReuseIdentifier:@"CollectionCell"];
    //Register a ReusableView of type uicollectionelementkinsectionheader
    [self.mainView registerClass:[headReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"headReusableView"];
    //Register a ReusableView of type UICollectionElementKindSectionFooter (tail)
//    [self.mainView registerClass:[footReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:@"footReusableView"];
  • Proxy for collectionView
#pragma mark sets the number of groups for CollectionView
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView{
    return 1;
#pragma mark sets the number of collectionviews in each group
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
    return self.Array.count;
#pragma mark sets the cell displayed by CollectionView
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
    CollectionCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"CollectionCell" forIndexPath:indexPath];
    cell.model = self.Array[indexPath.item];
    return cell;
#pragma mark defines the size of each UICollectionView
//- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath{
//    return CGSizeMake(100, 100);
#pragma mark defines the distance between the entire CollectionViewCell and the entire View
//- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section{
//    return UIEdgeInsetsMake(10, 10, 10, 10);
#pragma mark sets whether the CollectionViewCell can be clicked
//- (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath{
//    return YES;
#pragma mark click CollectionView to trigger the event
-(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
#pragma mark headView size
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section{
    return CGSizeMake(self.view.frame.size.width, 100);
//#pragma mark footView size
//- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForFooterInSection:(NSInteger)section{
//    return CGSizeMake(self.view.frame.size.width, 80);
#pragma mark headView and footView are all judged here
- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath{
    headReusableView *headerView = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"headReusableView" forIndexPath:indexPath];
//    footReusableView *footerView = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:@"footReusableView" forIndexPath:indexPath];
//    [footerView.btn1 addTarget:self action:@selector(btn1Click) forControlEvents:UIControlEventTouchUpInside];
    [headerView.Btn1 addTarget:self action:@selector(Btn1Click) forControlEvents:UIControlEventTouchUpInside];
//    if ([kind isEqualToString:UICollectionElementKindSectionHeader]) {
        return headerView;
//    }else{
//        return footerView;
//    }
  • Self defined proxy (to calculate the proportional width and height)
#pragma mark wxzFlowLayoutDelegate
- (CGFloat)WXZWaterFlow:(UICollectionViewFlowLayout *)waterFlow heightForWidth:(CGFloat)width atIndexPath:(NSIndexPath *)indexPath{
    shopModel *model = self.Array[indexPath.item];
    return model.h * width / model.w ;
  • In UICollectionViewFlowLayout.h
#import <UIKit/UIKit.h>
@class UICollectionViewFlowLayout;

@protocol WXZWaterFlowDelegte <NSObject>
- (CGFloat)WXZWaterFlow:(UICollectionViewFlowLayout *)waterFlow heightForWidth:(CGFloat)width atIndexPath:(NSIndexPath *)indexPath;

@interface FlowLayout : UICollectionViewFlowLayout

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

  • In UICollectionViewFlowLayout.m
#import "FlowLayout.h"

#define WXZCollectionW self.collectionView.frame.size.width

/** Spacing between lines */
static const CGFloat  WXZDefaultRowMargin = 5;
/** Spacing between columns */
static const CGFloat  WXZDefaultColumnMargin = 10;
/** top, left, bottom, right */
static const UIEdgeInsets  WXZDefaultInsets = {5, 15, 5, 5};
/** Default number of columns */
static const int WXZDefaultColumsCount = 2;

@interface FlowLayout()
/** Maximum Y value of each column */
@property (nonatomic, strong) NSMutableArray *columnMaxYs;
/** Store layout properties of all cell s */
@property (nonatomic, strong) NSMutableArray *attrsArray;

@property (nonatomic, strong) NSArray *heightArray;


@implementation FlowLayout
#pragma mark -lazy
- (NSMutableArray *)columnMaxYs
    if (!_columnMaxYs) {
        _columnMaxYs = [[NSMutableArray alloc] init];
    return _columnMaxYs;

- (NSMutableArray *)attrsArray
    if (!_attrsArray) {
        _attrsArray = [[NSMutableArray alloc] init];
    return _attrsArray;

- (NSArray *)heightArray
    if (!_heightArray) {
        _heightArray = [[NSArray alloc] init];
        _heightArray = @[@85,@105,@115,@105];
    return _heightArray;

#pragma mark - an internal approach
 * Determines the contentSize of the collectionView. Because collectionView delegates the layout task of an item to a layout object, the size of the scrolling area is unknown to it. The custom layout object must calculate the size of the display content in this method, including the supplementary view and the decoration view.
- (CGSize)collectionViewContentSize
    // Find the maximum Y value of the longest column
    CGFloat destMaxY = [self.columnMaxYs[0] doubleValue];
    for (NSUInteger i = 1; i<self.columnMaxYs.count; i++) {
        // Take the maximum Y value of column i
        CGFloat columnMaxY = [self.columnMaxYs[i] doubleValue];
        // Find the maximum value in the array
        if (destMaxY < columnMaxY) {
            destMaxY = columnMaxY;
    return CGSizeMake(0, destMaxY + WXZDefaultInsets.bottom);
 * This method will be called before the system is ready to layout the item. After we rewrite this method, we can preset the variable properties needed in the method. For example, before the layout of waterfall flow, we can initialize the array to store the height of waterfall flow. Sometimes we need to store layout attribute objects, such as card animation customization, or initialize arrays in this method. Remember to call [super prepareLayout];
- (void)prepareLayout
    [super prepareLayout];
    // Reset the maximum Y value of each column
    [self.columnMaxYs removeAllObjects];
    for (NSUInteger i = 0; i<WXZDefaultColumsCount; i++) {
        [self.columnMaxYs addObject:@(];
    // Calculate layout properties for all cell s
    [self.attrsArray removeAllObjects];
    NSUInteger count = [self.collectionView numberOfItemsInSection:0];
    for (NSUInteger i = 0; i < count; ++i) {
        NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
        UICollectionViewLayoutAttributes *attrs = [self layoutAttributesForItemAtIndexPath:indexPath];
        [self.attrsArray addObject:attrs];
 * Describes the layout properties of all elements (such as cell, supplementary control, decoration control). I don't think there is one of the most core methods to complete the customized layout. collectionView calls this method and passes the rectangle in its own coordinate system, which represents the visible range of the current collectionView. We need to return an array of UICollectionViewLayoutAttributes objects in this method. This layout attribute object determines the layout attributes including the size, hierarchy and visual attributes of the currently displayed item. At the same time, this method can also set the layout properties of supplementary view and decorationView. The premise of reasonable use of this method is not to return all properties casually, unless the view is within the visual range of the current collection view, or the user experience is reduced due to a large number of extra calculations - the reason for your overtime work.
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
    //Find the head reusableview of the collection view and add it to the array so that the child can be displayed in the head
    [self.attrsArray addObjectsFromArray:[super layoutAttributesForElementsInRect:rect]];
    return self.attrsArray;

 * It is an important method to explain the layout properties of cell. collectionView may request special layout properties for some special item s. We can create and return special customized layout properties in this method. Call the [uicollectionviewlayoutattributes layoutattributes withindexpath:] method according to the incoming indexPath to create the attribute object, and then set the created attribute, including the animation effects such as custom deformation and displacement
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
    UICollectionViewLayoutAttributes *attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
    /** Calculate layout properties of indexPath location cell */
    // Total spacing in horizontal direction
    CGFloat xMargin = WXZDefaultInsets.left + WXZDefaultInsets.right + (WXZDefaultColumsCount - 1) * WXZDefaultColumnMargin;
    // cell width
    CGFloat w = (WXZCollectionW - xMargin - 20) / WXZDefaultColumsCount;
    // cell height
    CGFloat h = [self.delegate WXZWaterFlow:self heightForWidth:w atIndexPath:indexPath];
    // To find out the column number and maximum Y value of the shortest column, you need to judge the first + 363
    CGFloat destMaxY = [self.columnMaxYs[0] doubleValue];
    NSUInteger destColumn = 0;
    for (NSUInteger i = 1; i<self.columnMaxYs.count; i++) {
        // Take the maximum Y value of column i
        CGFloat columnMaxY = [self.columnMaxYs[i] doubleValue];
        // Find the minimum value in the array
        if (destMaxY > columnMaxY) {
            destMaxY = columnMaxY;
            destColumn = i;
    // x value of cell
    CGFloat x = WXZDefaultInsets.left + destColumn * (w + WXZDefaultColumnMargin);
    CGFloat y = destMaxY + WXZDefaultRowMargin;
    // cell frame
    attrs.frame = CGRectMake(x, y, w, h);
    // y value of cell
    if (destMaxY==5) {
        //Manually increase the height of the first cell. If not, the cell starts from the top. Will overlap the head
        attrs.frame = CGRectMake(x, 120, w, h);
    // Update the maximum Y value in the array
    self.columnMaxYs[destColumn] = @(CGRectGetMaxY(attrs.frame));
    return attrs;

//- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
//When the boundaries of collectionView are changed, we need to tell collectionView whether to recalculate the layout properties and return the recalculated results through this method. A simple return to YES will cause our layout to be redrawn continuously every second, resulting in additional calculation tasks.
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds{
    //    NSLog(@"%s",__func__);
    return NO;
  • You can add anything in the header reusableview of the CollectionView, which inherits from UIView. If the FlowLayout of your CollectionView is customized by the system, you don't need to add the customized headReusableView to the array of FlowLayout. The system has done what you want.
  • CollectionView is very easy to use. It is recommended to learn more. There are the following extensions: for each picture, there are more or less text. So our height is still the height of the picture + the size of the font


Posted by r2ks on Thu, 30 Apr 2020 02:55:16 -0700