1

I have a small, single row horizontal layout UICollectionView at the top of the screen. It can contain up to a maximum of 6 items. The problem is that I want all 6 items visible without scrolling (this collection view is also going to be used in a Today extension which doesn't allow scrolling). What I want to do is reduce the cell-size and inter-item spacing a little bit to allow all 6 cells to fit.

Basically I'm trying to avoid this:

enter image description here

I've been playing with this for a while but I'm not sure how to approach it. I created a method that's fired every time an item is added or removed from the collection view, just before [self.collectionview reloadData] is called.

-(void)setupCollectionViewLayout{

     UICollectionViewFlowLayout *flowLayout = (UICollectionViewFlowLayout*)self.buttonBarCollectionView.collectionViewLayout;

        //Figure out if cells are wider than screen
    CGFloat screenwidth = self.view.frame.size.width;

    CGFloat sectionInsetLeft = 10;
    CGFloat sectionInsetRight = 10;
    CGFloat minItemSpacing = flowLayout.minimumInteritemSpacing;
    CGSize  itemsize = CGSizeMake(58,58);
    CGFloat itemsizeWidth = itemsize.width;
    CGFloat totalWidth = sectionInsetLeft + sectionInsetRight +
                        (itemsizeWidth * _armedRemindersArray.count) +
                        (minItemSpacing * (_armedRemindersArray.count -2));


    CGFloat reductionAmount = itemsizeWidth;
    if (totalWidth > screenwidth) {


        while (totalWidth > screenwidth) {
            totalWidth = totalWidth - 1;
             reductionAmount = reductionAmount - 1;
        }


        CGSize newCellSize = CGSizeMake(reductionAmount, reductionAmount);
        flowLayout.itemSize = newCellSize;

    }

    else flowLayout.itemSize = itemsize;

}

This is the result.

enter image description here

Not exactly what I was expecting. Not only did it squash everything to the left and also added a second line, but I also seem to have a cell-reuse issue. Truthfully I would just use static-cells if it was even an option, but unfortunately it seems like it's not possible.

What should I be doing? Subclassing UICollectionViewFlowLayout? Won't that basically do the same thing I'm doing here with the built-in flow layout?

EDIT: Kujey's answer is definitely closer to what I need. I still have a cell-reuse issue though. enter image description here

Joseph Toronto
  • 1,882
  • 1
  • 15
  • 29

3 Answers3

4

Xcode provides an object designed for your need. It's called UICollectionViewFlowLayout and all you need to do is subclass it and place your cells the way you want. The function prepareForLayout is call every time the collection view needs to update the layout. The piece of code you need is below :

#import "CustomLayout.h"

#define MainCell @"MainCell"

@interface CustomLayout ()

@property (nonatomic, strong) NSMutableDictionary *layoutInfo;

@end

@implementation CustomLayout


-(NSMutableDictionary *) layoutInfo
{
    if (!_layoutInfo) {
        _layoutInfo = [NSMutableDictionary dictionary];
    }

    return _layoutInfo;
}

-(void) prepareLayout
{
    NSMutableDictionary *cellLayoutInfo = [NSMutableDictionary dictionary];

    NSIndexPath *indexPath;

    CGFloat itemWidth;
    CGFloat itemSpacing;

    CGFloat widthWithoutSpacing = [self collectionViewContentSize].width / ([self.collectionView numberOfItemsInSection:0]);

    if (widthWithoutSpacing > [self collectionViewContentSize].height) {
        itemWidth = [self collectionViewContentSize].height;
        itemSpacing = ([self collectionViewContentSize].width - itemWidth*[self.collectionView numberOfItemsInSection:0])/
        ([self.collectionView numberOfItemsInSection:0]+1);
    }
    else {
        itemWidth = widthWithoutSpacing;
        itemSpacing = 0;
    }

    CGFloat xPosition = itemSpacing;

    for (NSInteger section = 0; section < [self.collectionView numberOfSections]; section++) {

        for (NSInteger index = 0 ; index < [self.collectionView numberOfItemsInSection:section] ; index++) {

            indexPath = [NSIndexPath indexPathForItem:index inSection:section];
            UICollectionViewLayoutAttributes *itemAttributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];

            CGRect currentFrame=itemAttributes.frame;

            currentFrame.origin.x = xPosition;
            currentFrame.size.width = itemWidth;
            currentFrame.size.height = itemWidth;

            itemAttributes.frame=currentFrame;
            cellLayoutInfo[indexPath] = itemAttributes;

            xPosition += itemWidth + itemSpacing;
        }
    }

    self.layoutInfo[MainCell] = cellLayoutInfo;
}


- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
{
    return YES;
}


- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
    NSMutableArray *allAttributes = [NSMutableArray arrayWithCapacity:self.layoutInfo.count];

    [self.layoutInfo enumerateKeysAndObjectsUsingBlock:^(NSString *elementIdentifier, NSDictionary *elementsInfo, BOOL *stop) {
        [elementsInfo enumerateKeysAndObjectsUsingBlock:^(NSIndexPath *indexPath, UICollectionViewLayoutAttributes *attributes, BOOL *innerStop) {
            if (CGRectIntersectsRect(rect, attributes.frame)) {
                [allAttributes addObject:attributes];
            }
        }];
    }];

    return allAttributes;
}


-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
    return self.layoutInfo[MainCell][indexPath];
}


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



@end

You can also change the y origin of your cells if you need to center them vertically.

Kujey
  • 1,122
  • 6
  • 17
  • This worked like magic. I'm not entirely sure how it works yet but I'm going to pour through it in a little bit. I would like to set a max-value for the cell size (when there's only one item it's gigantic), and I still seem to be having a cell-reuse issue. This gets me way closer to where I need to be though. Thanks! – Joseph Toronto Jan 25 '15 at 15:26
  • What kind of issue ? I kind of had my own struggle with it a while ago. Might be able to help – Kujey Jan 26 '15 at 13:11
  • The cells are being superimposed when I add more items to the collection view. It's as if it creates a new, smaller cell after calculating the new size yet leaves the big, pre-calculation one behind. Same thing that's happening in my above screenshot. – Joseph Toronto Jan 27 '15 at 01:12
  • Well i made an answer a while ago for this kind of problem. You're probably customizing your cell in a wrong way. Here is the link : http://stackoverflow.com/questions/23801418/uicollectionview-adding-image-to-a-cell/23802296#23802296 . You can add any kind of stuff the same way i did, as long as you remove it in your prepareForReuse – Kujey Jan 27 '15 at 02:00
  • Yep. That did the trick. I went the lazy [remove from superview] way. Seems to work great. – Joseph Toronto Jan 28 '15 at 00:03
  • I'm actually trying to change a few things but I'm having a bit of trouble understanding how it works. When there's only one item, it's slightly left to the center of the collection view. What I would like is to have the first item left-justified with 10 points of padding on the left and only start reducing the spacing as I run out of room. Thank you! – Joseph Toronto Jan 28 '15 at 05:14
0

try with this code. I get the width and use _armedRemindersArray (i guess you use this array for the items).

-(void)setupCollectionViewLayout{   
         UICollectionViewFlowLayout *flowLayout = (UICollectionViewFlowLayout*)self.buttonBarCollectionView.collectionViewLayout;

         //Figure out if cells are wider than screen
         CGFloat screenwidth = self.view.frame.size.width;
         CGFloat width = screenwidth - ((sectionInsetLeft + sectionInsetRight) *_armedRemindersArray.count  + minItemSpacing * (_armedRemindersArray.count -2));
         CGSize  itemsize = CGSizeMake(width,width);
         flowLayout.itemSize = itemsize;
}
Altimir Antonov
  • 4,966
  • 4
  • 24
  • 26
0

I don't know why you're setting the itemsize first, and then reducing it. I think you should do it the other way around:

-(void)setupCollectionViewLayout{

     UICollectionViewFlowLayout *flowLayout = (UICollectionViewFlowLayout*)self.buttonBarCollectionView.collectionViewLayout;

    CGFloat sectionInsetLeft = 10;
    CGFloat sectionInsetRight = 10;
    CGFloat minItemSpacing = flowLayout.minimumInteritemSpacing;

    // Find the appropriate item width (<= 58)
    CGFloat screenwidth = self.view.frame.size.width;
    CGFloat itemsizeWidth = (screenwidth - sectionInsetLeft - sectionInsetRight - (minItemSpacing * (_armedRemindersArray.count -2))) / _armedRemindersArray.count
    itemsizeWidth = itemsizeWidth > 58 ? 58 : itemsizeWidth;

    flowLayout.itemSize = CGSizeMake(itemsizeWidth, itemsizeWidth);
}

Does this work? If not, could you please include more of your code?

Armin
  • 1,150
  • 8
  • 24
  • Didn't work. For some reason itemsizeWidth ends up with an overflow value. Kujey's answer seems to work though. Thanks! – Joseph Toronto Jan 25 '15 at 15:27
  • Could it be because of `_armedRemindersArray.count` being 0 ? Anyways, glad you found an answer. – Armin Jan 25 '15 at 20:52