Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Todd Laney’s enhancements to Sticky Headers + UICollectionViewFlowLayout
//
// StickyHeaderLayout.h
// Wombat
//
// Created by Todd Laney on 1/9/13.
// Copyright (c) 2013 ToddLa. All rights reserved.
//
// Modified from http://blog.radi.ws/post/32905838158/sticky-headers-for-uicollectionview-using THANKS!
//
#import <UIKit/UIKit.h>
@interface StickyHeaderLayout : UICollectionViewFlowLayout
@end
//
// StickyHeaderFlowLayout.m
// Wombat
//
// Created by Todd Laney on 1/9/13.
// Copyright (c) 2013 ToddLa. All rights reserved.
//
// Modified from http://blog.radi.ws/post/32905838158/sticky-headers-for-uicollectionview-using THANKS!
//
#import "StickyHeaderFlowLayout.h"
@implementation StickyHeaderFlowLayout
- (NSArray *) layoutAttributesForElementsInRect:(CGRect)rect {
NSMutableArray *answer = [[super layoutAttributesForElementsInRect:rect] mutableCopy];
NSMutableIndexSet *missingSections = [NSMutableIndexSet indexSet];
for (NSUInteger idx=0; idx<[answer count]; idx++) {
UICollectionViewLayoutAttributes *layoutAttributes = answer[idx];
if (layoutAttributes.representedElementCategory == UICollectionElementCategoryCell) {
[missingSections addIndex:layoutAttributes.indexPath.section]; // remember that we need to layout header for this section
}
if ([layoutAttributes.representedElementKind isEqualToString:UICollectionElementKindSectionHeader]) {
[answer removeObjectAtIndex:idx]; // remove layout of header done by our super, we will do it right later
idx--;
}
}
// layout all headers needed for the rect using self code
[missingSections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:idx];
UICollectionViewLayoutAttributes *layoutAttributes = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader atIndexPath:indexPath];
[answer addObject:layoutAttributes];
}];
return answer;
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {
UICollectionViewLayoutAttributes *attributes = [super layoutAttributesForSupplementaryViewOfKind:kind atIndexPath:indexPath];
if ([kind isEqualToString:UICollectionElementKindSectionHeader]) {
UICollectionView * const cv = self.collectionView;
CGPoint const contentOffset = cv.contentOffset;
CGPoint nextHeaderOrigin = CGPointMake(INFINITY, INFINITY);
if (indexPath.section+1 < [cv numberOfSections]) {
UICollectionViewLayoutAttributes *nextHeaderAttributes = [super layoutAttributesForSupplementaryViewOfKind:kind atIndexPath:[NSIndexPath indexPathForItem:0 inSection:indexPath.section+1]];
nextHeaderOrigin = nextHeaderAttributes.frame.origin;
}
CGRect frame = attributes.frame;
if (self.scrollDirection == UICollectionViewScrollDirectionVertical) {
frame.origin.y = MIN(MAX(contentOffset.y, frame.origin.y), nextHeaderOrigin.y - CGRectGetHeight(frame));
}
else { // UICollectionViewScrollDirectionHorizontal
frame.origin.x = MIN(MAX(contentOffset.x, frame.origin.x), nextHeaderOrigin.x - CGRectGetWidth(frame));
}
attributes.zIndex = 1024;
attributes.frame = frame;
}
return attributes;
}
- (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {
UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForSupplementaryViewOfKind:kind atIndexPath:indexPath];
return attributes;
}
- (UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {
UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForSupplementaryViewOfKind:kind atIndexPath:indexPath];
return attributes;
}
- (BOOL) shouldInvalidateLayoutForBoundsChange:(CGRect)newBound {
return YES;
}
@end
@radoslaw-w

This comment has been minimized.

Copy link

radoslaw-w commented Sep 25, 2013

NSMutableArray *answer = [[super layoutAttributesForElementsInRect:rect] mutableCopy]; leaks in

  • (NSArray *) layoutAttributesForElementsInRect:(CGRect)rect

(NSMutableArray_) cast fixes that:
NSMutableArray *answer = (NSMutableArray_)[super layoutAttributesForElementsInRect:rect]

@someoneAnyone

This comment has been minimized.

Copy link

someoneAnyone commented Sep 30, 2013

In iOS7 there is a problem with the layout. Specifically, if the hosting ViewController is using edgesForExtendedLayout it messes with:

    CGPoint const contentOffset = cv.contentOffset;

This (contentOffset) reads as 0,0 when the content isn't allowed to extend under the top bars and works great however when you turn on under top bars the offset reads as 0,-64.

Here is how I proposed to fix it.

    UIEdgeInsets const contentEdgeInsets = cv.contentInset;
    CGPoint const contentOffset = CGPointMake(cv.contentOffset.x, cv.contentOffset.y + contentEdgeInsets.top
                                              );

Does that look ok? It seemed to work in my simulator.

@simplelife-patrick

This comment has been minimized.

Copy link

simplelife-patrick commented Nov 9, 2013

If here is 1 header view and 0 cell, you codes will "hide" the header view... if the count of cells increase (bigger than 0), and the "hidden" header view will be back... Why?

@r3econ

This comment has been minimized.

Copy link

r3econ commented May 5, 2014

This code crashes on line 36. Also on line 28 the index of the for loop is being modified inside of it. Bad practice. Although it might work for some, it's a code smell.

@haojianzong

This comment has been minimized.

Copy link

haojianzong commented Dec 11, 2014

I agree with @r3econ.
the implementation of layoutAttributesForElementsInRect: is not quite straight-forward.
I make a little modification at:
https://gist.github.com/haojianzong/209251d1fc171705bbee

@pomozoff

This comment has been minimized.

Copy link

pomozoff commented Jul 19, 2016

Very slow

@dollar2048

This comment has been minimized.

Copy link

dollar2048 commented Oct 26, 2016

sometimes next section should not show cells but header should be still visible.
it that case the code above hide header as well.

To fix that no need to check for
if (layoutAttributes.representedElementCategory == UICollectionElementCategoryCell)
because we need to add to missingSections everything (even header indexPath.section)

@pgpt10

This comment has been minimized.

Copy link

pgpt10 commented Dec 7, 2016

Can you please provide the code in Swift 3.0?

@poholo

This comment has been minimized.

Copy link

poholo commented Apr 9, 2018

Maybe can add the sectionInset of collectionView, like this:

`

    if (self.scrollDirection == UICollectionViewScrollDirectionVertical) {
        frame.origin.x += self.sectionInset.left;
        frame.origin.y = MIN(MAX(contentOffset.y, frame.origin.y) + self.sectionInset.bottom, nextHeaderOrigin.y + self.sectionInset.bottom - CGRectGetHeight(frame));
    } else { // UICollectionViewScrollDirectionHorizontal
        frame.origin.x = MIN(MAX(contentOffset.x, frame.origin.x) + self.sectionInset.left, nextHeaderOrigin.x + self.sectionInset.left - CGRectGetWidth(frame));
        frame.origin.y += self.sectionInset.top;
    }
    frame.size = CGSizeMake(frame.size.width - self.sectionInset.left - self.sectionInset.bottom, frame.size.height - self.sectionInset.top - self.sectionInset.bottom);`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.