Jonathan
Jonathan

Reputation: 742

Cells order in UICollectionView

I need to create a UICollectionView with cells of different sizes (1x1, 2x2, 2x1, 3x2.5). I've already code to add cells depending on which size they are using collectionView:layout:sizeForItemAtIndexPath:.

Here is expected result (cells 1 and 3 have to be on the left of cell 2) :
enter image description here

But current result is :
enter image description here

My (ugly) current code is (self.cellSize = screen width / 3) :

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {

    CGFloat size = self.cellSize;

    if (indexPath.item == 0) {
        return CGSizeMake(self.cellSize * 3.f, self.cellSize * 2.5f);
    }

    if (indexPath.item == 1) {
        return CGSizeMake(self.cellSize, self.cellSize);
    }

    if (indexPath.item == 2) {
        return CGSizeMake(self.cellSize * 2.f, self.cellSize * 2.f);
    }

    if (indexPath.item == 3) {
        return CGSizeMake(self.cellSize, self.cellSize);
    }

    if (indexPath.item == 4) {
        return CGSizeMake(self.cellSize * 2.f, self.cellSize);
    }

    if (indexPath.item == 5) {
        return CGSizeMake(self.cellSize, self.cellSize);
    }

    if (indexPath.item == 6) {
        return CGSizeMake(self.cellSize, self.cellSize);
    }

    if (indexPath.item == 7) {
        return CGSizeMake(self.cellSize * 2.f, self.cellSize);
    }

    if (indexPath.item == 8) {
        return CGSizeMake(self.cellSize * 3.f, self.cellSize * 2.5f);
    }

    return CGSizeMake(size, size);
}

Is there a way to specify that cell 3 has to be above cell 1, but on left of cell 2 ?

I don't want to do a static layout that will not change because each cell could be of different size in future. For example, cell 2 could be 2x1 sized ...

Which is the best approach to do that ? I was expecting to specify to UICollectionViewLayout a flow (like "from top"), but it doesn't work like that ...

Upvotes: 2

Views: 1567

Answers (2)

Y.Bonafons
Y.Bonafons

Reputation: 2349

Here it's the same logic as Larme suggested. But with less hardcode and which give you the possibility of setting the number of items you want without adding new case:.

I call "pattern" a set of 5 items. So first I define constant values:

#define MAX_COLUMN              3 // Max columns in the pattern
#define MAX_LINE_PER_PATTERN    3 // Max lines in the pattern
#define PATTERN_ITEM_COUNT      5 // Max items in the pattern

Then I create a custom layout with 2 properties NSMutableArray *layoutAttributes and CGFloat contentHeight in which I need to override the methods:

- (void)prepareLayout{
    [super prepareLayout];

    if (!self.layoutAttributes){
        self.layoutAttributes = [[NSMutableArray alloc] init];

        CGFloat cellWidth = self.collectionView.frame.size.width / MAX_COLUMN;
        CGFloat cellHeight = cellWidth;
        self.contentHeight = 0.f;

        for (int item = 0 ; item < [self.collectionView numberOfItemsInSection:0] ; item ++){

            CGFloat width, height = 0.f;
            CGFloat xPos, yPos = 0.f;

            NSInteger patternCount = (NSInteger)((CGFloat)item / (CGFloat)PATTERN_ITEM_COUNT);
            NSInteger currentIndex = item % PATTERN_ITEM_COUNT;
            switch (currentIndex) {
                case 0:
                {
                    xPos = 0.f;
                    yPos = 0.f + MAX_LINE_PER_PATTERN * cellHeight * patternCount;
                    width = cellWidth;
                    height = cellHeight;
                    self.contentHeight += cellHeight;
                    break;
                }
                case 1:
                {
                    xPos = cellWidth;
                    yPos = 0.f + MAX_LINE_PER_PATTERN * cellHeight * patternCount;
                    width = cellWidth * 2.f;
                    height = cellHeight * 2.f;
                    self.contentHeight += cellHeight;
                    break;
                }
                case 2:
                {
                    xPos = 0.f;
                    yPos = cellHeight + MAX_LINE_PER_PATTERN * cellHeight * patternCount;
                    width = cellWidth;
                    height = cellHeight;
                    break;
                }
                case 3:
                {
                    xPos = 0.f;
                    yPos = 2.f * cellHeight + MAX_LINE_PER_PATTERN * cellHeight * patternCount;
                    width = cellWidth * 2.f;
                    height = cellHeight;
                    self.contentHeight += cellHeight;
                    break;
                }
                case 4:
                {
                    xPos = 2.f * cellWidth;
                    yPos = 2.f * cellHeight + MAX_LINE_PER_PATTERN * cellHeight * patternCount;
                    width = cellWidth;
                    height = cellHeight;
                    break;
                }
                default:
                    NSLog(@"error with index");
                    break;
            }
            UICollectionViewLayoutAttributes *attr = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:[NSIndexPath indexPathForRow:item inSection:0]];
            attr.frame = CGRectMake(xPos,
                                    yPos,
                                    width,
                                    height);
            [self.layoutAttributes addObject:attr];
        }
    }
}

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect{
    NSMutableArray *currentAttributes = [NSMutableArray new];
    for (UICollectionViewLayoutAttributes *attr in self.layoutAttributes) {
        if (CGRectIntersectsRect(attr.frame, rect))
        {
            [currentAttributes addObject:attr];
        }
    }
    return currentAttributes;
}

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

Then assign this custom layout to self.collectionView.collectionViewLayout and that's it. You can find more information and swift version here.

Upvotes: 2

Larme
Larme

Reputation: 26086

A sample example by subclassing UICollectionViewLayout. All values are hard coded, just to explicit the logic behind it. Of course, that could be optimized.

@interface CustomCollectionViewLayout ()

@property (nonatomic, strong) NSMutableDictionary *cellLayouts;
@property (nonatomic, assign) CGSize              unitSize;

@end

@implementation CustomCollectionViewLayout


-(id)initWithSize:(CGSize)size
{
  self = [super init];
  if (self)
  {
    _unitSize = CGSizeMake(size.width/3,150);
    _cellLayouts = [[NSMutableDictionary alloc] init];
  }
  return self;
}
-(void)prepareLayout
{

  for (NSInteger i = 0; i < [[self collectionView] numberOfItemsInSection:0]; i ++)
  {
    NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
    UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
    CGRect frame;
    switch ([indexPath item])
    {
      case 0:
        frame = CGRectMake(0, 0, _unitSize.width*3, _unitSize.height*2.5);
        break;
      case 1:
        frame = CGRectMake(0, _unitSize.height*2.5, _unitSize.width, _unitSize.height);
        break;
      case 2:
        frame = CGRectMake(_unitSize.width, _unitSize.height*2.5, _unitSize.width*2, _unitSize.height*2);
        break;
      case 3:
        frame = CGRectMake(0, _unitSize.height*2.5+_unitSize.height, _unitSize.width, _unitSize.height);
        break;
      case 4:
        frame = CGRectMake(0, _unitSize.height*2.5+_unitSize.height+_unitSize.height, _unitSize.width*2, _unitSize.height);
        break;
      case 5:
        frame = CGRectMake(_unitSize.width*2, _unitSize.height*2.5+_unitSize.height+_unitSize.height, _unitSize.width, _unitSize.height);
        break;
      case 6:
        frame = CGRectMake(0, _unitSize.height*2.5+_unitSize.height+_unitSize.height+_unitSize.height, _unitSize.width, _unitSize.height);
        break;
      case 7:
        frame = CGRectMake(_unitSize.width, _unitSize.height*2.5+_unitSize.height+_unitSize.height+_unitSize.height, _unitSize.width*2, _unitSize.height);
        break;
      case 8:
        frame = CGRectMake(0, _unitSize.height*2.5+_unitSize.height+_unitSize.height+_unitSize.height+_unitSize.height, _unitSize.width*3, _unitSize.height*2.5);
        break;
      default:
        frame = CGRectZero;
        break;
    }
    [attributes setFrame:frame];
    [[self cellLayouts] setObject:attributes forKey:indexPath];
  }
}

-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
  NSMutableArray *retAttributes = [[NSMutableArray alloc] init];
  for (NSIndexPath *anIndexPath in [self cellLayouts])
  {
    UICollectionViewLayoutAttributes *attributes = [self cellLayouts][anIndexPath];
    if (CGRectIntersectsRect(rect, [attributes frame]))
    {
      [retAttributes addObject:attributes];
    }
  }
  return retAttributes;
}

-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{

  return [self cellLayouts][indexPath];
}

-(CGSize)collectionViewContentSize
{
  return CGSizeMake(_unitSize.width*3, _unitSize.height*9);
}

@end

Then, you just have to call :

CustomCollectionViewLayout *layout = [[CustomCollectionViewLayout alloc] initWithSize:self.myCollectionView.bounds.frame.size];
[self.myCollectionView setCollectionViewLayout:layout];

Rendering : enter image description here

Upvotes: 10

Related Questions