Ryan Hampton
Ryan Hampton

Reputation: 319

Swift: Issues with UICollection View layout

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.

Upvotes: 0

Views: 1156

Answers (1)

Tim Bernikovich
Tim Bernikovich

Reputation: 5945

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

Upvotes: 1

Related Questions