Stretchy UICollectionView headers

The bouncing effect of scroll views is probably one of the most distinctive things about iOS. While originally it was just eye candy over time it has actually inspired a few functional uses, like pull-to-refresh. Another cool use of the scroll view bounce that I like is the stretchy header.

As you pull the scroll view down more of the image is subtly revealed on the top and bottom. Slick! I’m sure you’ve seen this in many iOS apps before. Today I’m going to show you how to achieve this effect using a UICollectionView with a custom layout. Let’s get started.

First, create a subclass of UICollectionViewFlowLayout.

#import <UIKit/UIKit.h>
@interface StretchyHeaderCollectionViewLayout : UICollectionViewFlowLayout

If you’ve ever played around with custom collection view layouts then you’ve probably learned that they are quite powerful. Here is our implementation.

#import "StretchyHeaderCollectionViewLayout.h"
@implementation StretchyHeaderCollectionViewLayout
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {
    // This will schedule calls to 
    // layoutAttributesForElementsInRect: while scrolling. 
    return YES;
- (UICollectionViewScrollDirection)scrollDirection {
    // This subclass only supports vertical scrolling.
    return UICollectionViewScrollDirectionVertical;
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
    UICollectionView *collectionView = [self collectionView];
    UIEdgeInsets insets = [collectionView contentInset];
    CGPoint offset = [collectionView contentOffset];
    CGFloat minY =;
    // First get the superclass attributes.
    NSArray *attributes = [super layoutAttributesForElementsInRect:rect];
    // Check if we've pulled below past the lowest position
    if (offset.y < minY) {  
        // Figure out how much we've pulled down 
        CGFloat deltaY = fabsf(offset.y - minY);
        for (UICollectionViewLayoutAttributes *attrs in attributes) {
            // Locate the header attributes
            NSString *kind = [attrs representedElementKind];
            if (kind == UICollectionElementKindSectionHeader) {
                // Adjust the header's height and y based on how 
                // much the user has pulled down.
                CGSize headerSize = [self headerReferenceSize];
                CGRect headerRect = [attrs frame];
                headerRect.size.height = MAX(minY, headerSize.height + deltaY);
                headerRect.origin.y = headerRect.origin.y - deltaY;
                [attrs setFrame:headerRect];
    return attributes;

This custom layout will check to see if we’ve pulled down beyond the lowest possible offset, which means that we’re stretching it. If we are, locate the header element and increase its height and offset its y position by the stretched amount.

Next, wherever you set up your collection view, add the following code:

// Create a new instance of our stretchy layout and set the 
// default size for our header (for when it's not stretched)
StretchyHeaderCollectionViewLayout *stretchyLayout;
stretchyLayout = [[StretchyHeaderCollectionViewLayout alloc] init];
[stretchyLayout setHeaderReferenceSize:CGSizeMake(320.0, 160.0)];
// Set our custom layout
[collectionView setCollectionViewLayout:stretchyLayout];
// and tell our collection view to always bounce.
[collectionView setAlwaysBounceVertical:YES];
// Then register a class to use for the header.
[collectionView registerClass:[UICollectionReusableView class]

The last thing we need to do is create our header which should be a UICollectionReusableView. We can add a UIImageView to it as a subview and use springs and struts to keep it sized properly.

- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView 
           viewForSupplementaryElementOfKind:(NSString *)kind 
                                 atIndexPath:(NSIndexPath *)indexPath {
    // You can make header an ivar so we only ever create one
    if (!header) {
        header = [collectionView dequeueReusableSupplementaryViewOfKind:kind
        CGRect bounds;
        bounds = [header bounds];
        UIImageView *imageView;
        imageView = [[UIImageView alloc] initWithFrame:bounds];
        [imageView setImage:[UIImage imageNamed:@"header-background"]];
        // Make sure the contentMode scales proportionally
        [imageView setContentMode:UIViewContentModeScaleAspectFill];
        // Clip the parts of the image that are not in frame
        [imageView setClipsToBounds:YES];
        // Set the autoresizingMask to always be the same height as the header
        [imageView setAutoresizingMask:UIViewAutoresizingFlexibleHeight];
        // Add the image to our header
        [header addSubview:imageView];
    return header;

The autoresizingMask will keep the image height the same as the header. The contentMode and clipsToBounds properties will center the image and clip the rest while maintaining the aspect ratio.

You can view the full source and a sample project on Github.