简体   繁体   中英

iOS UICollectionView special cell order

I have a simple collection view with 5 elements. The default order of the cells looks like this: 在此输入图像描述

I wanna know if it's possible to change the padding\\order of the cells to get result like this:

在此输入图像描述

Option 1: (Clean option)

You should have 2 sections .

  • Section 1 with 3 cells

  • Section 2 with 2 cells

You can then adjust the inset with collection​View(_:​layout:​inset​For​Section​At:​) for the section you want to adjust. (in this case, section 2)

If you do not implement this method, the flow layout uses the value in its section​Inset property to set the margins instead. Your implementation of this method can return a fixed set of margin sizes or return different margin sizes for each section.

Section insets are margins applied only to the items in the section. They represent the distance between the header view and the first line of items and between the last line of items and the footer view. They also indicate they spacing on either side of a single line of items. They do not affect the size of the headers or footers themselves.

Option 2: (Spaghetti option, but good to know)

Create a custom subclass for section 2 items, where you can customize the inset of the actual content for the UICollectionViewCell contentView subviews.

Then in section 2, return your customized cell.

To do this, you may need to write a custom layout. Check out the Collection View Programming Guide for more information.

The default (flow) layout will always lay the cells out in this pattern:

You can achieve this type of format in UICollectionView by changing the number of sections from 1 to 2.

And then you can define your custom UICollectionViewFlowLayout accordingly for different sections.

SOLUTION: I've created a subclass of UICollectionView named "CenteringCollectionView" and with a few calculations of the sections I made it!

The .h file:

#import <UIKit/UIKit.h>

@class CenteringCollectionView;
@protocol CenteringCollectionViewDelegate <NSObject>

-(void)collectionView:(CenteringCollectionView *)collectionView didDequeueReusableCell:(UICollectionViewCell *)cell indexPath:(NSIndexPath *)indexPath;

@end

@interface CenteringCollectionView : UICollectionView

@property (nonatomic, strong) NSMutableArray *dataSourceArr;
@property (nonatomic, weak) id<CenteringCollectionViewDelegate> delegateCenteringCollection;

@end

The .m file:

#import "CenteringCollectionView.h"

@interface CenteringCollectionView () <UICollectionViewDelegate, UICollectionViewDataSource>

@property (nonatomic, assign) IBInspectable long elementsInRow;
@property (nonatomic, assign) long elementsInRowInitialValue;
@property (nonatomic) IBInspectable NSString *cellIdentifier;
@property (nonatomic) IBInspectable CGFloat cellRelativeSize; // 0..1
@property (nonatomic, assign) long numOfSections;
@property (nonatomic, assign) IBInspectable BOOL autoResize; // *** If we want auto resize - we need to set the height constraint of the collection view in size of 1 line only even if we have more than 1 line (section).
@property (nonatomic, assign)IBInspectable CGFloat heightMiddleSpacing;
@property (nonatomic, assign) long cellSize;
//@property (nonatomic, assign) CGFloat verticalTopInset;
@property (nonatomic, assign) CGFloat initialHeightConstraint;
@property (nonatomic, weak) NSLayoutConstraint *selfHeightConstraint;
@property (nonatomic, assign) CGFloat cellSpacing;
@property (nonatomic, assign) BOOL shouldReloadUIElements;

// UI IBInspectable

@property (nonatomic, weak) IBInspectable UIColor *runtimeColor;

@end

static long const maxElementsInRowDefault = 3;

@implementation CenteringCollectionView

-(instancetype)initWithCoder:(NSCoder *)aDecoder
{
    if (self = [super initWithCoder:aDecoder])
    {
        self.elementsInRow = maxElementsInRowDefault; // will get the default value if not stored value in storyboard
        self.elementsInRowInitialValue = self.elementsInRow;
        self.cellRelativeSize = 0.5;
        self.initialHeightConstraint = -1;
    }
    return self;
}

-(void)setDataSourceCount:(long)dataSourceCount
{
    if (dataSourceCount == _dataSourceCount)
    {
        return;
    }

    _dataSourceCount = dataSourceCount;
    self.shouldReloadUIElements = YES;
    self.elementsInRow = MIN(self.elementsInRowInitialValue, self.dataSourceCount);
    self.numOfSections = ceil((CGFloat)self.dataSourceCount / (CGFloat)self.elementsInRow);

    CGFloat selfHeight = [self handleAutoResizeAndReturnTheNewHeightIfNeeded];
    CGFloat selfWidth = CGRectGetWidth(self.frame);

    CGFloat cellWidth = (selfWidth / self.elementsInRow) * self.cellRelativeSize;
    CGFloat cellHeight = (selfHeight / self.numOfSections) * self.cellRelativeSize;

    self.cellSize = MIN(cellWidth, cellHeight);

    dispatch_async(dispatch_get_main_queue(), ^{

        [self setCollectionView];
        [self reloadData];
    });
}

-(void)awakeFromNib
{
    [super awakeFromNib];
    self.elementsInRowInitialValue = self.elementsInRow;
    [self handleUIelementsIBInspectable];
}

-(void)handleUIelementsIBInspectable
{
    if (self.runtimeColor)
    {
        [self setBackgroundColor:self.runtimeColor];
    }
}

-(CGFloat)handleAutoResizeAndReturnTheNewHeightIfNeeded
{
    if (self.autoResize)
    {
        for (NSLayoutConstraint *constraint in [self constraints])
        {
            if (constraint.firstAttribute == NSLayoutAttributeHeight)
            {
                if (self.initialHeightConstraint == -1) // not set yet
                {
                    self.initialHeightConstraint = constraint.constant;
                }

                if (!self.selfHeightConstraint)
                {
                    self.selfHeightConstraint = constraint;
                }


                CGFloat newHeight = self.initialHeightConstraint * self.numOfSections;
                constraint.constant = newHeight;

                if (self.bounds.size.height != newHeight)
                {
                    CGRect frame = self.bounds;
                    frame.size.height = newHeight;
                    [self setBounds:frame];
                }

                dispatch_async(dispatch_get_main_queue(), ^{
                    [self.superview layoutIfNeeded];
                    [self layoutIfNeeded];
                });

                return newHeight;
            }
        }
    }

    return CGRectGetHeight(self.frame);
}

-(long)numOfSpacesInRow
{
    return self.elementsInRow + 1;
}

-(long)numOfSpacesBetweenLines
{
    return self.numOfSections + 1;
}

-(void)setCellRelativeSize:(CGFloat)cellRelativeSize
{
    _cellRelativeSize = MAX(0, MIN(cellRelativeSize, 1));
}

-(void)setCollectionView
{
    [self reloadData];
    UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
    CGFloat horizontalCellSpacing = ((CGRectGetWidth(self.frame) - (self.cellSize * self.elementsInRow)) / self.numOfSpacesInRow);
    CGFloat verticalCellSpacing = (CGRectGetHeight(self.frame) - (self.numOfSections * self.cellSize)) / self.numOfSpacesBetweenLines;

    self.cellSpacing = MAX(MIN(horizontalCellSpacing, verticalCellSpacing), 0);
    [layout setMinimumInteritemSpacing:self.cellSpacing];
    [layout setMinimumLineSpacing:self.cellSpacing];
    [layout setScrollDirection:UICollectionViewScrollDirectionVertical];
    [self setCollectionViewLayout:layout];

    self.showsVerticalScrollIndicator = NO;
    self.showsHorizontalScrollIndicator = NO;
    self.scrollEnabled = NO;

    if (!self.delegate)
    {
        self.delegate = self;
        self.dataSource = self;
    }
}

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
    return CGSizeMake(self.cellSize, self.cellSize);
}

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
    BOOL isLastSection = (section == self.numOfSections - 1);
    if (isLastSection == NO)
    {
        return self.elementsInRow;
    }
    else
    {
        long numOfLeftItemsInLastRow = self.dataSourceCount % self.elementsInRow;
        if (numOfLeftItemsInLastRow == 0)
        {
            return self.elementsInRow;
        }
        else
        {
            return  numOfLeftItemsInLastRow;
        }
    }
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:self.cellIdentifier forIndexPath:indexPath];
    if ([self.delegateCenteringCollection respondsToSelector:@selector(collectionView:didDequeueReusableCell:indexPath:)])
    {
        [self.delegateCenteringCollection collectionView:self didDequeueReusableCell:cell indexPath:[self indexPathWithoutSectionsFrom:indexPath]];
    }
    return cell;
}

-(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
    if ([self.delegateCenteringCollection respondsToSelector:@selector(collectionView:didSelectItemAtIndexPath:cell:)])
    {
        UICollectionViewCell *selectedCell = [collectionView cellForItemAtIndexPath:indexPath];
        [self.delegateCenteringCollection collectionView:self didSelectItemAtIndexPath:[self indexPathWithoutSectionsFrom:indexPath] cell:selectedCell];
    }
}

-(NSIndexPath *)indexPathWithoutSectionsFrom:(NSIndexPath *)indexPath
{
    long sectionNum = indexPath.section;
    long rowNum = sectionNum * self.elementsInRow + indexPath.row;
    NSIndexPath *newIndexPath = [NSIndexPath indexPathForRow:rowNum inSection:0];
    return newIndexPath;
}

-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
    return self.numOfSections;
}

-(void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath
{
    dispatch_async(dispatch_get_main_queue(), ^{
        if (self.shouldReloadUIElements == NO)
        {
            return;
        }

        if (self.autoResize && self.selfHeightConstraint)
        {
            BOOL isTheFirstCellInTheLastSection = (indexPath.section == self.numOfSections - 1) && indexPath.row == 0;
            if (isTheFirstCellInTheLastSection)
            {
                CGFloat newHeight = CGRectGetMaxY(cell.frame) + self.cellSpacing;

                self.selfHeightConstraint.constant = newHeight;

                if (self.bounds.size.height != newHeight)
                {
                    CGRect frame = self.bounds;
                    frame.size.height = newHeight;
                    [self setBounds:frame];
                }

                [self.superview layoutIfNeeded];
                [self layoutIfNeeded];
            }
        }
    });
}

- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section
{
    if (self.shouldReloadUIElements == NO)
    {
        return collectionView.contentInset;
    }

    NSInteger cellsCount = [collectionView numberOfItemsInSection:section];
    CGFloat horizontalInset = (collectionView.bounds.size.width - (cellsCount * self.cellSize) - ((cellsCount - 1) * self.cellSpacing)) * 0.5;
    horizontalInset = MAX(horizontalInset, 0.0);

    BOOL isLastSection = (section == self.numOfSections - 1);

    CGFloat verticalTopInset = self.cellSpacing;

    CGFloat verticalBottomInset = verticalTopInset;

    if (section == 0 && isLastSection == NO)
    {
        if (self.heightMiddleSpacing)
        {
            verticalBottomInset += self.heightMiddleSpacing;
        }
        verticalBottomInset /= 2;
    }

    if (section > 0)
    {
        if (self.heightMiddleSpacing)
        {
            verticalTopInset += self.heightMiddleSpacing;
        }
        verticalTopInset /= 2;

        if (isLastSection == NO)
        {
            if (self.heightMiddleSpacing)
            {
                verticalBottomInset += self.heightMiddleSpacing;
            }
            verticalBottomInset /= 2;
        }
    }

    return UIEdgeInsetsMake(verticalTopInset, horizontalInset, verticalBottomInset, horizontalInset);
}

@end

And to make it work, all we need to do in the parent view is:

self.collectionView.delegateCenteringCollection = self;
self.collectionView.dataSourceCount = 5; // Or whatever number we want!

In the storyboard: we need to create collectionView of this class and set the "elements in row" value, also set the "Cell Identifier" and the "Cell relative size" between 0 to 1 (the "Cell relative size" value: will calculate the cell size & paddings according to the collectionView width & height). And at last - set "autoResize" to "true" if you want that the collection view will resize its own height constraint(if exist) automatically according to the number of rows. If we set "autoResize" to true, the height constraint that we set to the collectionView will determine the height of a single row. If our collectionView should grow for example to 3 rows, it will multiple our collectionview height constraint by 3.

And it works like a charm!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM