How to make sticky headers in UICollectionView on iOS7? - ios

I'm using this solution to make sticky headers in collection view and it works well on iOS6. But in iOS7 if the "Extend edges under top bars" option is checked then the header sticks to the origin of the view which is then behind the navigation bar.
So for now I disabled that option but I'd really like to tweak that solution to make it work when the edges are extended (in order to have the nice blurry effect when the content scrolls behind the navigation bar).
Can someone help me with that? Or does someone have any other suggestions to accomplish the same goal?

Here is my full solution. Just add the following to your FlowLayout class:
#pragma mark STICKY HEADERS CODE BELOW
- (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 || layoutAttributes.representedElementCategory == UICollectionElementCategorySupplementaryView) {
[missingSections addIndex:(NSUInteger) 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) {
[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;
CGFloat topOffset = 0;
if ([self.collectionView.dataSource isKindOfClass:[UIViewController class]]) {
UIViewController *collectionViewParentViewController = (UIViewController *)self.collectionView.dataSource;
topOffset = collectionViewParentViewController.topLayoutGuide.length;
}
CGPoint const contentOffset = CGPointMake(cv.contentOffset.x, cv.contentOffset.y + topOffset);
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 {
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;
}

Related

UICollectionView: custom FlowLayout crash if empty sections

I have a problem using custom flow layout, if my sections are empty i got a "EXC_ARTHMETIC(code=EXC_I386_DIV, subcode=0x0)".
I'm using custom FlowLayout to add custom header and custom section background(the crash is related to the background).
some code (please look at layoutAttributesForBackgroundAtSection: to see where the crash happens):
// UIViewController
-(void)loadView
{
[super loadView];
collectionViewFlowLayout *flowLayout = [[collectionViewFlowLayout alloc]init];
flowLayout.sectionInset = UIEdgeInsetsMake(35, 20, 0, 20);
[flowLayout setItemSize:cellSize];
[flowLayout setScrollDirection:UICollectionViewScrollDirectionVertical];
flowLayout.minimumInteritemSpacing = 0.0f;
self.collectionView = [[UICollectionView alloc]initWithFrame:(CGRectZero) collectionViewLayout:flowLayout];
[self.collectionView setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.collectionView setBackgroundColor:[UIColor colorWithRed:246./256. green:246./256. blue:246./256. alpha:1]];
[self.view addSubview:self.collectionView];
[[self.collectionView.topAnchor constraintEqualToAnchor:self.view.topAnchor] setActive:YES];
[[self.collectionView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor] setActive:YES];
[[self.collectionView.leftAnchor constraintEqualToAnchor:self.view.leftAnchor] setActive:YES];
[[self.collectionView.rightAnchor constraintEqualToAnchor:self.view.rightAnchor] setActive:YES];
[self.collectionView registerClass:[collectionViewCell class] forCellWithReuseIdentifier:reuseCellIdentifier];
[self.collectionView registerClass:[sectionHeaderReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:reuseHeaderIdentifier];
[self.collectionView registerClass:[sectionBackgroundReusableView class]
forSupplementaryViewOfKind:KindSectionBackground
withReuseIdentifier:NSStringFromClass([sectionBackgroundReusableView class])];
[self.collectionView setShowsHorizontalScrollIndicator:NO];
[self.collectionView setShowsVerticalScrollIndicator:YES];
[self.collectionView setDataSource:self];
[self.collectionView setDelegate:self];
}
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
return 6;
}
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return 0;
}
-(UICollectionReusableView *)collectionView:(UICollectionView *)collectionV
viewForSupplementaryElementOfKind:(NSString *)kind
atIndexPath:(NSIndexPath *)indexPath
{
UICollectionReusableView *reusableview = nil;
if (kind == UICollectionElementKindSectionHeader)
{
reusableview = [collectionV dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:reuseHeaderIdentifier forIndexPath:indexPath];
NSString *sectionName = self.shelves[indexPath.section];
[(sectionHeaderReusableView*)reusableview configureWithTitle:sectionName];
}
if (kind == UICollectionElementKindSectionFooter)
{
}
if (kind == KindSectionBackground)
{
reusableview = [collectionView dequeueReusableSupplementaryViewOfKind:sectionBackground
withReuseIdentifier:NSStringFromClass([sectionBackgroundReusableView class])
forIndexPath:indexPath];
}
return reusableview;
}
-(CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section
{
return = KHeaderSize;
}
// FloawLayout subclass
-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSMutableArray *attributes = [NSMutableArray arrayWithArray:[super layoutAttributesForElementsInRect:rect]];
// 1. get visible sections
NSInteger lastIndex = -1;
for(UICollectionViewLayoutAttributes * attr in attributes) {
lastIndex = attr.indexPath.section;
UICollectionViewLayoutAttributes * attr = [self layoutAttributesForBackgroundAtSection:lastIndex];
[attributes addObject:attr];
}
return attributes;
}
-(UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)kind
atIndexPath:(NSIndexPath *)indexPath
{
if([kind isEqualToString:KindSectionBackground]) {
return [self layoutAttributesForBackgroundAtSection:indexPath.section];
} else {
return [super layoutAttributesForSupplementaryViewOfKind:kind atIndexPath:indexPath];
}
}
-(UICollectionViewLayoutAttributes *)layoutAttributesForBackgroundAtSection:(NSUInteger)section
{
NSIndexPath * indexPath =[NSIndexPath indexPathForItem:0
inSection:section];
UICollectionViewLayoutAttributes * attr = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:KindSectionBackground
withIndexPath:indexPath];
attr.hidden = NO;
attr.zIndex = -1;
// crash happen in the line below.
UICollectionViewLayoutAttributes * firstAttr = [selflayoutAttributesForItemAtIndexPath:indexPath];
CGRect frame;
frame.origin.x = firstAttr.frame.origin.x - self.sectionInset.left;
frame.origin.y = firstAttr.frame.origin.y - self.sectionInset.top;
frame.size.width = self.collectionView.bounds.size.width;
NSUInteger numItems = [self.collectionView numberOfItemsInSection:section];
CGFloat cellsPerLine = floorf(self.collectionView.bounds.size.width / self.itemSize.width);
NSUInteger numLines = ceilf(numItems / (float)cellsPerLine);
frame.size.height = numLines * firstAttr.size.height + (numLines-1)*self.minimumLineSpacing +
self.sectionInset.top + self.sectionInset.bottom;
attr.frame = frame;
return attr;
}
Could you please help to fix this, thank you.
Looks like layoutAttributesForItemAtIndexPath crashes when the section provided in the IndexPath is empty. This is probably a bug since it's supposed to return an optional (at least in swift). I had the same kind of issue and fixed it by checking the number of items in the section (swift example for section 1):
if dataSource.collectionView(collectionView, numberOfItemsInSection: 1) > 0 {
if let layoutAttributes = self.layoutAttributesForItem(at: IndexPath(row: 0, section: 1)) {
// update layoutAttributes or create your background attributes
}
}
One workaround would be to call collectionView.reloadData() in advance before we call any layout methods, this work for me.

viewForSupplementaryElementOfKind not being called on custom UICollectionViewLayout

I created a custom UICollectionViewLayout and I've put it in my Collection View Controller in the storyboard. I didn't get that much problems on displaying the items/cells. My problem is that the viewForSupplementaryElementOfKind is not being called. I can't to seem to pinpoint on why is that. I tried to go back to the default layout and it does call it then but I need my custom UICollectionViewLayout. I've left NSLogs to check and viewForSupplementaryElementOfKind is not being called.
Here is my MyCollectionViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
viewArray = [NSMutableArray array];
//setup the delegate for vertical flow layout
[(VerticalFlowLayout*)self.collectionViewLayout setDelegate:self];
//register the headers
[self.collectionView registerNib:[ButtonHeaderCollectionReusableView nib] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:[ButtonHeaderCollectionReusableView reusableID]];
//set the insets
[self.collectionView setContentInset:UIEdgeInsetsMake(23, 5, 10, 5)];
}
#pragma mark - CollectionView DataSource
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return [self.cardsArray count];
}
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
NSLog(#"Number of Sections");
return 1;
}
- (UICollectionViewCell*)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"Making Card");
CardCell *card = [collectionView dequeueReusableCellWithReuseIdentifier:#"CardCell" forIndexPath:indexPath];
// UILabel *cardLabel = [card viewWithTag:101];
// [cardLabel setText:[textArray objectAtIndex:[indexPath row]]];
return card;
}
- (UICollectionReusableView*)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {
NSLog(#"Making Header");
UICollectionReusableView *reusableView = nil; /*[[UICollectionReusableView alloc] initWithFrame:CGRectMake(0, 0, 100, 75)];*/
// [reusableView setBackgroundColor:[UIColor blackColor]];
if (kind == UICollectionElementKindSectionHeader) {
ButtonHeaderCollectionReusableView *headerview = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:[ButtonHeaderCollectionReusableView reusableID] forIndexPath:indexPath];
return headerview;
}
// if (kind == UICollectionElementKindSectionFooter) {
// UICollectionReusableView *footerview = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionFooter withReuseIdentifier:#"FooterView" forIndexPath:indexPath];
// }
return reusableView;
}
- (CGSize)collectionView:collectionView layout:(nonnull UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section {
return CGSizeMake(180.0f, 72.0f);
}
Now here is my MyFlowLayout.m
- (id)initWithCoder:(NSCoder *)aDecoder {
NSLog(#"Vertical Flow Layout init with coder");
if(self = [super initWithCoder:aDecoder]) {
contentHeight = 0;
cachedAttributes = [NSMutableArray array];
}
return self;
}
- (void)prepareLayout {
NSLog(#"Preparing Layout");
// [super prepareLayout];
contentWidth = CGRectGetWidth(self.collectionView.bounds) - (self.collectionView.contentInset.left + self.collectionView.contentInset.right);
if([cachedAttributes count] == 0) {
//compute for column width
CGFloat columnWidth = contentWidth / NUMBER_OF_COLUMNS;
NSMutableArray *xOffsets = [NSMutableArray array];
for(int i = 0; i < NUMBER_OF_COLUMNS; i++) {
[xOffsets addObject:[NSNumber numberWithFloat:(i * columnWidth)]];
}
//compute for height
NSMutableArray *yOffsets = [NSMutableArray array];
for(int i = 0; i < NUMBER_OF_COLUMNS; i++) {
[yOffsets addObject:[NSNumber numberWithFloat:75]];
}
int column = 0;
//loop through all the sections and items in the collectionview
for(int i = 0; i < self.collectionView.numberOfSections; i++) {
for(int j = 0; j < [self.collectionView numberOfItemsInSection:i]; j++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:j inSection:i];
//time to do some frame calculation
///let's start with the width
CGFloat width = columnWidth - CELL_PADDING * 2;
///then the height
CGFloat viewHeight = [self.delegate getViewHeightWithCollectionView:self.collectionView indexPath:indexPath withWidth:width];
CGFloat height = CELL_PADDING + viewHeight + /*textHeight +*/ CELL_PADDING;
CGFloat xOffset = [[xOffsets objectAtIndex:column] floatValue];
CGFloat yOffset = [[yOffsets objectAtIndex:column] floatValue];
CGRect frame = CGRectMake(xOffset, yOffset, columnWidth, height);
CGRect insetFrame = CGRectInset(frame, CELL_PADDING, CELL_PADDING);
//now that computation is done we shall make the attributes
UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
[attributes setFrame:insetFrame];
[cachedAttributes addObject:attributes];
//time to increment the height and the column
contentHeight = MAX(contentHeight, CGRectGetMaxY(frame));
NSNumber *yOffSetColumn = [yOffsets objectAtIndex:column];
yOffSetColumn = [NSNumber numberWithFloat:([yOffSetColumn floatValue] + height)];
[yOffsets replaceObjectAtIndex:column withObject:yOffSetColumn];
NSLog(#"Content Height: %f yOffSetColumn: %# == %#", contentHeight, yOffSetColumn, [yOffsets objectAtIndex:column]);
column = column >= (NUMBER_OF_COLUMNS - 1) ? 0 : ++column;
}
}
}
}
- (CGSize)collectionViewContentSize {
// NSLog(#"Collection View Content Size");
return CGSizeMake(contentWidth, contentHeight + 75);
}
// called after preparelayout to determine which items are visible in the given rect
- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {
NSMutableArray<UICollectionViewLayoutAttributes *> *layoutAttributes = [NSMutableArray array];
NSInteger sectionsCount = [self.collectionView.dataSource numberOfSectionsInCollectionView:self.collectionView];
NSLog(#"Sections count: %ld", (long)sectionsCount);
for(int i = 0; i < sectionsCount; i++) {
//for header
UICollectionViewLayoutAttributes *headerAttributes = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader atIndexPath:[NSIndexPath indexPathForItem:0 inSection:i]];
// if(CGRectIntersectsRect(headerAttributes.frame, rect)) {
// NSLog(#"Adding Section Attribute");
[layoutAttributes addObject:headerAttributes];
// }
for (UICollectionViewLayoutAttributes *attributes in cachedAttributes) {
if(CGRectIntersectsRect(attributes.frame, rect)) {
[layoutAttributes addObject:attributes];
}
}
// NSInteger itemsCount = [self.collectionView numberOfItemsInSection:i];
// for(int j = 0; j < itemsCount; j++) {
// UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForItemAtIndexPath:[NSIndexPath indexPathForItem:j inSection:i]];
// if(CGRectIntersectsRect(attributes.frame, rect)) {
// [layoutAttributes addObject:attributes];
// }
// }
}
return layoutAttributes;
}
//- (UICollectionViewLayoutAttributes*)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
// NSLog(#"Layout Attributes For Item: %ld %ld", [indexPath row], [indexPath section]);
//
// UICollectionViewLayoutAttributes *attributes = [cachedAttributes objectAtIndex:[indexPath row]];
// return attributes;
//}
- (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath {
NSLog(#"Layout Attributes For Header: %# %ld %ld", elementKind, [indexPath row], [indexPath section]);
UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
[attributes setFrame:CGRectMake(0, 0, contentWidth /*320*/, 75.0f)];
return attributes;
}
As you can see in MyFlowLayout, there are things I've tried but still it didn't call viewForSupplementaryElementOfKind. I tried implementing layoutAttributesForItemAtIndexPath since I have layoutAttributesForSupplementaryViewOfKind. I tried also using fixed numbers like the 75 and 320 that you see just to check if the header would be generated. The delegate that you see, it's just for getting a height of a view.
Since I've given the header enough space to be seen and left NSLogs all over and yet (UICollectionReusableView*)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath is not being called.
Here are some that I've looked and tried: stackoverflow but as you can see from the commented code that I've included. It still didn't work.
Thanks for the help.
I also had a problem with this and my solution was to simply add attributes for section in prepareLayout
UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader withIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]];
attributes.frame = CGRectMake(0, 0, self.collectionView.frame.size.width, 100);
[cachedAttributes addObject:attributes];
Add the default size of header section:
collectionViewLayout.headerReferenceSize = CGSize(width, height)
The default size values are (0, 0). Thats why header is not displaying.
OR
There is also another way for it, implement the delegate method for UICollectionView:
collectionView(_:layout:referenceSizeForFooterInSection:)

iOS UICollectionView header & footer location

Working in iOS 7, how does one specify where the header & footer boxes go in a UICollectionView?
I have a custom UICollectionViewFlowLayout. I have overwritten
-(void)prepareLayout
-(NSArray*) layoutAttributesForElementsInRect:(CGRect)rect
-(UICollectionViewLayoutAttributes*) layoutAttributesForSupplementaryViewOfKind: (NSString*)kind atIndexPath:(NSIndexPath*)indexPath
My problem is, I'm not sure how to specify header location. I have already specified that a header exists in prepareLayout:
-(void)prepareLayout
{
[super prepareLayout];
boundsSize = self.collectionView.bounds.size;
midX = boundsSize.width / 2.0f;
curIndex = 0;
self.headerReferenceSize = CGSizeMake(CELL_SIZE, TITLE_HEIGHT);
self.footerReferenceSize = CGSizeMake(0, 0);
self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
self.sectionInset = UIEdgeInsetsMake(TOP_INSET, LEFT_INSET, BOTTOM_INSET, RIGHT_INSET);
self.minimumLineSpacing = LINE_SPACING;
self.minimumInteritemSpacing = INTERIM_SPACING;
self.itemSize = CGSizeMake(CELL_SIZE, CELL_SIZE);
}
I just don't know the right property of my custom FlowLayout to set, as there doesn't seem to be something like "HeaderLocation" to set, either as a LayoutAttributes or in the layout object itself. Right now, it is appearing to the side/between my images, when I'd like them to be appearing above each image (horizontal scroll).
I have tried the following:
-(UICollectionReusableView*) collectionView: (UICollectionView*)collectionView viewForSupplementaryElementOfKind:(NSString*)kind atIndexPath:(NSIndexPath*)indexPath
{
NSLog(#"**ViewForSupplementaryElementOfKind called***");
CGFloat centerX = collectionView.center.x;
CGFloat centerY = collectionView.center.y;
CGFloat titleWidth = [MyLayout titleWidth];
CGFloat titleHeight = [MyLayout titleHeight];
MyTitleView* titleView = [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:ImageTitleIdentifier forIndexPath:indexPath];
titleView.frame = CGRectMake(centerX - titleWidth/2.0,
0.0,
titleWidth,
titleHeight);
return titleView;
}
This doesn't work. The title appears above overlapped with a bunch of other titles, then the moment I start scrolling (horizontally), they jump back into the wrong place, horizontally between the images rather than above.
PS> Please do not suggest anything that has to do with NIB or XIB placement. I am using a UICollectionView, NOT a UICollectionViewController, so I actually have no prototypical cell to work with. The layout is being done entirely programatically -- from code alone -- so I can't simply open a XIB file and adjust the location of a text box.
Amending the attributes returned by -layoutAttributesForElementsInRect is the right approach, but if you want to alter the position of offscreen headers and footers, you may need to fetch the supplementary view attributes yourself.
For example, in your UICollectionViewFlowLayout subclass:
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSMutableArray *attributesArray = [[super layoutAttributesForElementsInRect:rect] mutableCopy];
// the call to super only returns attributes for headers that are in the bounds,
// so locate attributes for out of bounds headers and include them in the array
NSMutableIndexSet *omittedSections = [NSMutableIndexSet indexSet];
for (UICollectionViewLayoutAttributes *attributes in attributesArray) {
if (attributes.representedElementCategory == UICollectionElementCategoryCell) {
[omittedSections addIndex:attributes.indexPath.section];
}
}
for (UICollectionViewLayoutAttributes *attributes in attributesArray) {
if ([attributes.representedElementKind isEqualToString:UICollectionElementKindSectionHeader]) {
[omittedSections removeIndex:attributes.indexPath.section];
}
}
[omittedSections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:idx];
UICollectionViewLayoutAttributes *attributes = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader
atIndexPath:indexPath];
[attributesArray addObject:attributes];
}];
for (UICollectionViewLayoutAttributes *attributes in attributesArray) {
if ([attributes.representedElementKind isEqualToString:UICollectionElementKindSectionHeader]) {
// adjust any aspect of each header's attributes here, including frame or zIndex
}
}
return attributesArray;
}
CollectionView Header height is set below Collectionview delegate
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section
And Set view in Collectionview Header in Below Delegate
- (UICollectionReusableView*)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{
UICollectionReusableView * view = nil;
if ([kind isEqualToString:UICollectionElementKindSectionHeader])
{
ColorSectionHeaderView *header = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader
withReuseIdentifier:NSStringFromClass([ColorSectionHeaderView class])
forIndexPath:indexPath];
header.sectionIndex = indexPath.section;
header.hideDelete = collectionView.numberOfSections == 1; // hide when only one section
header.delegate = self;
view = header;
}
return view;
}
Ragistred Class in ViewDidLoad
-(void)ViewDidLoad
{
[collectionView registerNib:[UINib nibWithNibName:NSStringFromClass([ColorSectionFooterView class]) bundle:nil]
forSupplementaryViewOfKind:UICollectionElementKindSectionFooter
withReuseIdentifier:NSStringFromClass([ColorSectionFooterView class])];
[Super ViewDidLoad];
}

First row Top spacing UICollectioViewCell

i'm having problems to align the first row to the top when I have different UICollectionViewCell heights and I don't know why. It seems that minimumLineSpacingForSectionAtIndex method is not working.
Here is my result:
As you can see, the first row is not aligned to the top.
This is my code:
- (void)viewDidLoad
{
...
FlowLayout *flowLayout = [[FlowLayout alloc] init];
flowLayout.itemSize = CGSizeMake(152, 260);
flowLayout.sectionInset = UIEdgeInsetsMake( 5, 5, 5, 5);
flowLayout.scrollDirection = UICollectionViewScrollDirectionVertical;
self.collectionView.collectionViewLayout = flowLayout;
...
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:
(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)
indexPath{
if (indexPath.row == 0)
return CGSizeMake(152, 200);
if (indexPath.row == 1)
return CGSizeMake(152, 270);
return CGSizeMake(152, 300);
}
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:
(UICollectionViewLayout*)collectionViewLayout
minimumInteritemSpacingForSectionAtIndex:(NSInteger)section {
return 5.0;
}
- (CGFloat)collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout*)collectionViewLayout
minimumLineSpacingForSectionAtIndex:(NSInteger)section {
return 8.0;
}
This is the code of my FlowLayout:
#implementation FlowLayout
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSMutableArray *result = [NSMutableArray array];
NSInteger sectionCount = 1;
if ([self.collectionView.dataSource respondsToSelector:#selector(numberOfSectionsInCollectionView:)])
{
sectionCount = [self.collectionView.dataSource numberOfSectionsInCollectionView:self.collectionView];
}
for (int s = 0; s < sectionCount; s++)
{
NSInteger itemCount = [self.collectionView.dataSource collectionView:self.collectionView numberOfItemsInSection:s];
for (int i = 0; i < itemCount; i++)
{
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:s];
UICollectionViewLayoutAttributes *layoutAttributes =
[self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader atIndexPath:indexPath];
if (layoutAttributes != nil) {
[result addObject:layoutAttributes];
}
UICollectionViewLayoutAttributes *layout = [self layoutAttributesForItemAtIndexPath:indexPath];
if (CGRectIntersectsRect(rect, layout.frame))
{
[result addObject:layout];
}
}
}
return result;
}
- (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;
}
- (BOOL) shouldInvalidateLayoutForBoundsChange:(CGRect)newBound {
return YES;
}
What you are experiencing is a normal cell placement when using the UICollectionViewFlowLayout.
You have to tweak the layout attributes being returned by the flow layout. To do so, override the layoutAttributesForElementsInRect: method and any of the methods that return layout attributes. Modify the attributes provided by the parent class, and then return them. The attribute you have to modify is frame.
A good example of doing it is a sample code from Apple - Custom Layouts: A Worked Example
Update after attaching implementation of the layout.
Looks like in the layoutAttributesForItemAtIndexPath: method you are not adjusting the frame for first item (index 0). You are returning the attributes you get from parent class. Change the frame's origin.y to 0 for item 0.

UICollectionView with section headers

Does anyone have an idea why I am getting this Error,
"No visible #interface for 'UICollectionViewController' declares the
selector 'layoutAttributesForSupplementaryViewOfKind
any help would be greatly appreciated..
Not sure where the error is coming from, I am getting it on the lines that have
[super layoutAttributesForElementsInRect:..
- (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;
}
Well the method don't exist in UITableViewController. It's a class level method on UICollectionViewLayoutAttributes so if you want to change it you need to subclass that class and also it's a class level method so use '+' not '-'.
All these methods have to be implemented by your subclass of UICollectionViewLayout, not by your controller.
These methods allow the UICollectionView to ask the subclass of UICollectionViewLayout how to position the cells and the supplementary views (headers for example).
You need 2 different classes, a subclass of UICollectionViewLayout and a controller that implements the UICollectionViewDelegate and UICollectionViewDataSource protocols and that provides the data (ie. number of cells, number of sections, the actual cells, etc.).
The particular error that you see is caused by this line:
[self layoutAttributesForSupplementaryViewOfKind:kind atIndexPath:indexPath];
self (ie. UICollectionViewController) does not define this method in its .h (and it shouldn't).

Resources