0

I am trying to layout a UICollectionView like the mock-up I have drawn in the photo(also showing the index of each item):

enter image description here

The code I currently have to try and achieve this is:

In ViewDidLoad:

collectionView.dataSource = self
    collectionView.delegate = self

    flowLayout.scrollDirection = .horizontal
    flowLayout.minimumLineSpacing = 5
    flowLayout.minimumInteritemSpacing = 5
    flowLayout.sectionInset = UIEdgeInsetsMake(0, 0, 0, 5)

Then later on the the file:

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    let totalWidth = collectionView.bounds.size.width
    let totalHeight = collectionView.bounds.size.height
    let heightOfView = totalHeight / 3
    let numberOfCellsPerRow = 1
    let dimensions = CGFloat(Int(totalWidth) / numberOfCellsPerRow)
    if (indexPath.item == 4) {
        return CGSize(width: collectionView.bounds.size.width, height: heightOfView)
    } else {
        return CGSize(width: dimensions / 2, height: heightOfView)
    }
}

This code isn't having the desired effect, and is producing a UICollectionView that looks like this:

enter image description here

As you can see, the cells are not even in the UICollectionView, with the right-hand side cells overflowing into scroll space. The section gaps between the items are wrong and the 5th, the larger cell is on the right-hand side of the screen out of view unless scrolled to.

5
  • you used flowLayout.scrollDirection = .horizontal are you sure this is a horizontal scroll? Commented Dec 21, 2016 at 11:20
  • Vertical scroll makes them all line up one after another vertically Commented Dec 21, 2016 at 11:25
  • Try to add the minimumLineSpacing and minimumInteritemSpacing in your calculation of the size. Commented Dec 21, 2016 at 12:41
  • Thats solves the clipping of the right hand side items. Do you know what is causing the large spacing between the rows and why the 5th item is on the top row instead of forming its own row? Commented Dec 21, 2016 at 13:23
  • Maybe the sectionInset. Try to not set it? Check also the contentFrame and the frame of you collectionView. Commented Dec 21, 2016 at 15:46

1 Answer 1

1

You cannot create such layout with standard UICollectionViewFlowLayout class. First of all watch WWDC videos related to UICollectionView and it's layout: https://developer.apple.com/videos/play/wwdc2012/219/. Then you can check Swift tutorials with sample code to get started: http://swiftiostutorials.com/tutorial-creating-custom-layouts-uicollectionview/

Simplest working, but not best structured will be such code in custom UICollectionViewLayout. Use this as a starting point:

@interface UICollectionViewCustomLayout ()

@property (nonatomic) NSArray<UICollectionViewLayoutAttributes *> *attributes;
@property (nonatomic) CGSize size;

@end

@implementation UICollectionViewCustomLayout

- (void)prepareLayout
{
    [super prepareLayout];

    NSMutableArray<UICollectionViewLayoutAttributes *> *attributes = [NSMutableArray new];

    id<UICollectionViewDelegate> delegate = (id<UICollectionViewDelegate>)self.collectionView.delegate;
    id<UICollectionViewDataSource> dataSource = (id<UICollectionViewDataSource>)self.collectionView.dataSource;

    NSInteger count = [dataSource collectionView:self.collectionView numberOfItemsInSection:0];
    CGFloat collectionViewWidth = CGRectGetWidth(self.collectionView.frame);
    CGFloat collectionViewHeight = CGRectGetHeight(self.collectionView.frame);
    CGFloat rowHeight = floor(collectionViewHeight / 3);
    NSUInteger numberOfPages = count / 5;
    if (count % 5) {
        numberOfPages++;
    }

    for (NSInteger item = 0; item < count; item++) {
        NSIndexPath *indexPath = [NSIndexPath indexPathForItem:item inSection:0];
        UICollectionViewLayoutAttributes *attribute = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];

        NSInteger index = item % 5;
        NSInteger page = item / 5;
        CGFloat width = index == 4 ? collectionViewWidth : collectionViewWidth / 2;
        CGSize size = CGSizeMake(width, rowHeight);
        CGFloat x = page * collectionViewWidth + (index % 2 == 0 ? 0 : collectionViewWidth / 2);
        CGFloat y = (index / 2) * rowHeight;
        CGPoint origin = CGPointMake(x, y);
        CGRect frame = CGRectZero;
        frame.size = size;
        frame.origin = origin;
        attribute.frame = frame;
        [attributes addObject:attribute];
    }
    self.attributes = attributes;

    self.size = CGSizeMake(numberOfPages * collectionViewWidth, collectionViewHeight);
}

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
    NSMutableArray<UICollectionViewLayoutAttributes *> *result = [NSMutableArray new];
    for (UICollectionViewLayoutAttributes *attribute in self.attributes) {
        if (CGRectIntersectsRect(attribute.frame, rect)) {
            [result addObject:attribute];
        }
    }
    return result;
}

- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"indexPath.section == %@ AND indexPath.item == %@", @(indexPath.section), @(indexPath.item)];
    UICollectionViewLayoutAttributes *attributes = [self.attributes filteredArrayUsingPredicate:predicate].firstObject;
    return attributes;
}

- (CGSize)collectionViewContentSize
{
    return self.size;
}

@end

And controller:

// Views
#import "UICollectionViewCustomLayout.h"

@interface ViewController () <UICollectionViewDataSource, UICollectionViewDelegate>

@property (nonatomic) UICollectionView *collectionView;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:[UICollectionViewCustomLayout new]];
    self.collectionView.backgroundColor = [UIColor whiteColor];
    self.collectionView.frame = self.view.bounds;
    self.collectionView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    self.collectionView.delegate = self;
    self.collectionView.dataSource = self;
    [self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:NSStringFromClass([UICollectionViewCell class])];
    [self.view addSubview:self.collectionView];
}

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
    return 10;
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(nonnull NSIndexPath *)indexPath
{
    NSArray *colors = @[[UIColor redColor], [UIColor blueColor], [UIColor grayColor], [UIColor greenColor], [UIColor purpleColor], [UIColor cyanColor]];

    UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:NSStringFromClass([UICollectionViewCell class]) forIndexPath:indexPath];
    cell.contentView.backgroundColor = colors[arc4random() % colors.count];
    cell.contentView.alpha = (arc4random() % 500 + 500) / 1000.0;
    return cell;
}

@end

enter image description here

Sign up to request clarification or add additional context in comments.

4 Comments

Thanks for the reply. Is there another way I could achieve this style of layout if FlowLayout won't work?
I've had a look at the code and I think I will just modify it to a 6 item grid as it seems complicated. Do you know how I can solve the large spacing between rows and make it so the collectionView is 3 items down and 2 items across?
@RyanHampton oh no, I haven't noticed, that question has swift tag. But I cannot rewrite it write now.
Thanks for your time, I'll have a go at trying to convert the code to swift

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.