viewForSupplementaryElementOfKind not being called on custom UICollectionViewLayout

Revin picture Revin · Mar 9, 2016 · Viewed 11.7k times · Source

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.

Answer

Rocky picture Rocky · Jul 31, 2017

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:)