Adam Yanalunas
Adam Yanalunas

Reputation: 59

Multiple supplementary views in a UICollectionViewFlowLayout?

I've got a fairly standard grid layout using a subclass of UICollectionViewFlowLayout. When the user taps a cell I move the cells in the subsequent rows down to make space for a detail view that will appear. It looks like this:

grid mockup

In that detail view I want to show another view with related data, similar to how iTunes shows album details. The existing layout has headers for each section and I'm currently slapping the detail view into place, manually managing the frame's position. This gets tricky with rotations and cells moving around.

How can I convince the layout to handle the detail's position by treating it as a supplementary view? My controller is configured correctly to display the detail as a supplementary view, same for the layout.

Upvotes: 1

Views: 689

Answers (1)

Adam Yanalunas
Adam Yanalunas

Reputation: 59

Solved the problem. In broad strokes, here's what works for me:

  1. Create a subclass of UICollectionViewLayoutAttributes and register it in your layout by overriding the layoutAttributesClass function.
  2. In layoutAttributesForElementsInRect: retrieve all standard layout attributes with [super layoutAttributesForElementsInRect:rect];.
  3. Copy that array into a mutable array and append another set of attributes for the supplementary view doing something like [attributesCopy addObject:[self layoutAttributesForSupplementaryViewOfKind:YourSupplementaryKind atIndexPath:indexPathForTappedCell]];
  4. In layoutAttributesForSupplementaryViewOfKind:atIndexPath: get the attributes for your view with something like YourLayoutAttributes *attributes = [YourLayoutAttributes layoutAttributesForSupplementaryViewOfKind:elementKind withIndexPath:indexPath];
  5. Test that the elementKind matches the type of supplementary view you want to generate
  6. Retrieve the cell's layout attributes using [self layoutAttributesForItemAtIndexPath:indexPath];
  7. Change the frame from the cell's attributes to suit the needs of your supplementary view
  8. Assign the new frame to the supplementary attributes

The important part to note is you can't skip subclassing UICollectionViewLayoutAttributes. Doing asking super (a UICollectionViewFlowLayout instance) for attributes of a supplemental view for anything but standard header or footers will return nil. I couldn't find any concrete documentation on this behavior so I might be wrong but in my experience it was the subclassed attributes that solved my problems.

Your code should look something like this:

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
    NSArray *allAttributesInRect = [super layoutAttributesForElementsInRect:rect];
    NSMutableArray *attributes = NSMutableArray.array;

    for (UICollectionViewLayoutAttributes *cellAttributes in allAttributesInRect)
    {
        // Do things with regular cells and supplemental views
    }

    if (self.selectedCellPath)
    {
        UICollectionViewLayoutAttributes *detailAttributes = [self layoutAttributesForSupplementaryViewOfKind:SomeLayoutSupplimentaryDetailView atIndexPath:self.selectedCellPath];
        [attributes addObject:detailAttributes];
    }

    return attributes.copy;
}


- (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath
{
    SomeLayoutAttributes *attributes = [SomeLayoutAttributes layoutAttributesForSupplementaryViewOfKind:elementKind withIndexPath:indexPath];

    if ([elementKind isEqualToString:SomeLayoutSupplimentaryDetailView])
    {
        UICollectionViewLayoutAttributes *cellAttributes = [self layoutAttributesForItemAtIndexPath:indexPath];
        CGRect frame = cellAttributes.frame;

        frame.size.width = CGRectGetWidth(self.collectionView.frame); // or whatever works for you
        attributes.frame = frame;
    }

    return attributes;
}

Your UICollectionViewLayoutAttributes subclass doesn't necessarily need any extra properties or functions but it's a great place to store data specific to that view for configuration use after you retrieve the view using dequeueReusableSupplementaryViewOfKind:forIndexPath:.

Upvotes: 2

Related Questions