Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UICollectionView with a sticky header

I found a blog on how to make sticky headers and it works great. Only thing is I don't think it takes into account the sectionInserts.

This is how its intended to look:

enter image description here

I have my inserts:

collectionViewFlowLayout.sectionInset = UIEdgeInsetsMake(16, 16, 16, 16); 

With the sticky header, it is moved down by 16 pixles:

enter image description here

I tried tinking with the original code and I think the issue is with the last part:

layoutAttributes.frame = (CGRect){     .origin = CGPointMake(origin.x, origin.y),     .size = layoutAttributes.frame.size 

If i change it to origin.y - 16, the header will start in the right location but when pushed up, 16 pixels of the head go off screen:

enter image description here

I'm not sure how to get it to take into account sectionInsects. Can anybody help?

Here is the code in full from the blog:

- (NSArray *) layoutAttributesForElementsInRect:(CGRect)rect {      NSMutableArray *answer = [[super layoutAttributesForElementsInRect:rect] mutableCopy];     UICollectionView * const cv = self.collectionView;     CGPoint const contentOffset = cv.contentOffset;      NSMutableIndexSet *missingSections = [NSMutableIndexSet indexSet];     for (UICollectionViewLayoutAttributes *layoutAttributes in answer) {         if (layoutAttributes.representedElementCategory == UICollectionElementCategoryCell) {             [missingSections addIndex:layoutAttributes.indexPath.section];         }     }     for (UICollectionViewLayoutAttributes *layoutAttributes in answer) {         if ([layoutAttributes.representedElementKind isEqualToString:UICollectionElementKindSectionHeader]) {             [missingSections removeIndex:layoutAttributes.indexPath.section];         }     }      [missingSections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {          NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:idx];          UICollectionViewLayoutAttributes *layoutAttributes = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader atIndexPath:indexPath];          [answer addObject:layoutAttributes];     }];      for (UICollectionViewLayoutAttributes *layoutAttributes in answer) {          if ([layoutAttributes.representedElementKind isEqualToString:UICollectionElementKindSectionHeader]) {              NSInteger section = layoutAttributes.indexPath.section;             NSInteger numberOfItemsInSection = [cv numberOfItemsInSection:section];              NSIndexPath *firstCellIndexPath = [NSIndexPath indexPathForItem:0 inSection:section];             NSIndexPath *lastCellIndexPath = [NSIndexPath indexPathForItem:MAX(0, (numberOfItemsInSection - 1)) inSection:section];              UICollectionViewLayoutAttributes *firstCellAttrs = [self layoutAttributesForItemAtIndexPath:firstCellIndexPath];             UICollectionViewLayoutAttributes *lastCellAttrs = [self layoutAttributesForItemAtIndexPath:lastCellIndexPath];              CGFloat headerHeight = CGRectGetHeight(layoutAttributes.frame);             CGPoint origin = layoutAttributes.frame.origin;             origin.y = MIN(                 MAX(                     contentOffset.y,                     (CGRectGetMinY(firstCellAttrs.frame) - headerHeight)                 ),                 (CGRectGetMaxY(lastCellAttrs.frame) - headerHeight)             );              layoutAttributes.zIndex = 1024;             layoutAttributes.frame = (CGRect){                 .origin = origin,                 .size = layoutAttributes.frame.size             };         }     }      return answer; } 
like image 936
Padin215 Avatar asked Mar 05 '13 20:03

Padin215


People also ask

What is swift UICollectionView?

An object that manages an ordered collection of data items and presents them using customizable layouts.

What is the difference between uicollectionflowlayout and uicollectionview?

So, we decided to change to UICollectionView, which is much more customisable. The UICollectionView offers greater customisation, allowing you to set a custom UICollectionFlowLayout. Unfortunately, the grid layout scrolls along one axis only; either horizontally or vertically.

Is it possible to scroll the uicollectionviewlayout in UIScrollView?

Unfortunately, the grid layout scrolls along one axis only; either horizontally or vertically. We hoped we could embedded the collection inside a UIScrollView, allowing us to scroll in both direction, but after some attempts we decided to subclass the UICollectionFlowLayout superclass: UICollectionViewLayout.

What is the difference between uicollectionview and UITableView?

It was really painful to work with the UITableView to make the necessary customisation. So, we decided to change to UICollectionView, which is much more customisable.

How to scroll only one direction in UIViewController?

Tick on ‘Scrolling Enabled’ and if you want to scroll only in one direction each time tick on ‘Direction Lock Enabled’. Otherwise, you could scroll in both directions at the same time. 4. Finally, open the Connections Inspector and connect the UICollectionView referencing outlet to your UIViewController.


2 Answers

Simplest solution for iOS 9 + as it doesn't need of writing subclass of UICollectionViewFlowLayout.

In viewDidLoad of viewController with collectionView use following code:

let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout // casting is required because UICollectionViewLayout doesn't offer header pin. Its feature of UICollectionViewFlowLayout layout?.sectionHeadersPinToVisibleBounds = true 
like image 90
Bibek Avatar answered Oct 02 '22 21:10

Bibek


Fix by Todd Laney to handle Horizontal and Vertical scrolling and to take into account the sectionInsets:

https://gist.github.com/evadne/4544569

@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];         if (layoutAttributes != nil) {             [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 
like image 20
rainerkohlberger Avatar answered Oct 02 '22 22:10

rainerkohlberger