10

I am using a UICollectionView to present a grid of images in an iPhone app (iOS6).

I am using vertical scrolling for the UICollectionView, and the images all have fixed width and varying height. The width of the images are set so that on an iPhone, it displays 3 columns of images. This works ok, and I get the images presented in a grid view.

However, since my images have varying height, the vertical spacing between images in a column varies and this doesn't look very good, as you can see in the following image (a mockup made in HTML):

Image showing how the images are currently laid out

I would instead like to achieve a more fluid flow, where the vertical spacing between images in a column are the same. The following mockup shows how I would like it to work:

Example of how I would like the flow of images to work

Any ideas of how to solve this?

Also, as a bonus question, does anyone know a way to solve the same problem if the app was not built for iOS6 (since UICollectionView is only available in iOS6). Either by using a 3rd party component or by solving it with standard iOS controls.

Jonas
  • 625
  • 1
  • 6
  • 18

2 Answers2

9

Subclass UICollectionViewFlowLayout and in that class add these methods. (Note this assumes a vertical orientation, and it skips the first line, and it is a constant 10 pixels between them. See: how do you determine spacing between cells in UICollectionView flowLayout if you need the horizontal version

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
NSArray* arr = [super layoutAttributesForElementsInRect:rect];
for (UICollectionViewLayoutAttributes* atts in arr) {
    if (nil == atts.representedElementKind) {
        NSIndexPath* ip = atts.indexPath;
        atts.frame = [self layoutAttributesForItemAtIndexPath:ip].frame;
    }
}
return arr;
}

- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewLayoutAttributes* atts =
[super layoutAttributesForItemAtIndexPath:indexPath];

if (indexPath.item == 0 || indexPath.item == 1) // degenerate case 1, first item of section
    return atts;

NSIndexPath* ipPrev =
[NSIndexPath indexPathForItem:indexPath.item-2 inSection:indexPath.section];

CGRect fPrev = [self layoutAttributesForItemAtIndexPath:ipPrev].frame;
CGFloat rightPrev = fPrev.origin.y + fPrev.size.height + 10;
if (atts.frame.origin.y <= rightPrev) // degenerate case 2, first item of line
    return atts;

CGRect f = atts.frame;
f.origin.y = rightPrev;
atts.frame = f;
return atts;
}
Community
  • 1
  • 1
Rob R.
  • 3,629
  • 2
  • 14
  • 12
  • 1
    Thanks for the help with this! For others looking at this, the answer solutions works. In the end I ended up using a 3rd party component instead, https://github.com/ptshih/PSCollectionView. This allowed me to target iOS5 too. – Jonas Feb 08 '13 at 10:15
  • 1
    I understood how to give spacing. But how can I set the size of individual cell in above code? – Satyam Sep 13 '13 at 12:42
  • 2
    @Satyamsvv Use the delegate method for UICollectionViewFlowLayout in your ViewController class (not the layout subclass) - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath – nsuinteger Feb 13 '14 at 03:30
  • Is this working for anyone? I'm getting a jumbled mess. – SirRupertIII Mar 05 '15 at 20:25
  • `Use of unresolved identifier 'sectionInset'` Error while compiling above code. Also crashing at `for atts:UICollectionViewLayoutAttributes in arr! {` as `arr` is `nil`. Using Swift version of it. – Sharad Chauhan Apr 15 '20 at 10:04
  • Hey Sharad it's probably best if you create a new question. This was answered 7 years ago before Swift was a thing and while I still had access to a Mac. I'm sorry I can't help at this point. – Rob R. Apr 15 '20 at 14:16
5

For Swift 3 this code work for me, including the space of first line to top...

override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
    let arr = super.layoutAttributesForElements(in: rect)

    for atts:UICollectionViewLayoutAttributes in arr! {
        if nil == atts.representedElementKind {
            let ip = atts.indexPath
            atts.frame = (self.layoutAttributesForItem(at: ip)?.frame)!
        }
    }
    return arr
}

override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
    let atts = super.layoutAttributesForItem(at: indexPath)

    if indexPath.item == 0 || indexPath.item == 1 {
        var frame = atts?.frame;
        frame?.origin.y = sectionInset.top;
        atts?.frame = frame!;
        return atts
    }

    let ipPrev = IndexPath(item: indexPath.item - 2, section: indexPath.section)

    let fPrev = self.layoutAttributesForItem(at: ipPrev)?.frame

    let rightPrev = (fPrev?.origin.y)! + (fPrev?.size.height)! + 10

    if (atts?.frame.origin.y)! <= rightPrev {
        return atts
    }

    var f = atts?.frame
    f?.origin.y = rightPrev
    atts?.frame = f!
    return atts
}
  • `Use of unresolved identifier 'sectionInset'` Error while compiling above code. Also crashing at `for atts:UICollectionViewLayoutAttributes in arr! {` as `arr` is `nil`. – Sharad Chauhan Apr 15 '20 at 09:44