Вопрос

I'm trying to implement a single sticky header in a UICollectionView.

My sticky header behavior is a bit different than the usual one you can see e.g. in UITableView. I have 3 headers in the collection view and I want only one of them to be sticky and stick to the top when the content is scrolled.

My code works pretty well. However, when I scroll down, the sticky header disappears suddenly at some point. Scrolling back makes the header appear again. What am I doing wrong?

I am attaching a implementation of my custom layout. It's a subclass of UICollectionViewFlowLayout.

@implementation CustomFlowLayout


- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
    NSMutableArray *attributes = [[super layoutAttributesForElementsInRect:rect] mutableCopy];
    CGPoint const contentOffset = self.collectionView.contentOffset;

    for (UICollectionViewLayoutAttributes *layoutAttributes in attributes)
    {
        // Adjust the sticky header frame.
        if ([layoutAttributes.representedElementKind isEqualToString:UICollectionElementKindSectionHeader] &&
            layoutAttributes.indexPath.section == SectionWithStickyHeadeIndex)
        {
            NSInteger numberOfItemsInSection = [self.collectionView numberOfItemsInSection:SectionWithStickyHeadeIndex];
            NSIndexPath *firstObjectIndexPath = [NSIndexPath indexPathForItem:0
                                                                    inSection:SectionWithStickyHeadeIndex];
            UICollectionViewLayoutAttributes *firstObjectAttrs;

            if (numberOfItemsInSection > 0)
            {
                firstObjectAttrs = [self layoutAttributesForItemAtIndexPath:firstObjectIndexPath];
            }
            else
            {
                firstObjectAttrs = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader
                                                                        atIndexPath:firstObjectIndexPath];
            }

            CGPoint origin = layoutAttributes.frame.origin;

            // Adjust the header origin so it sticks to the top.
            origin.y = MAX(contentOffset.y + self.collectionView.contentInset.top,
                           CGRectGetMinY(firstObjectAttrs.frame) - CGRectGetHeight(layoutAttributes.frame));

            layoutAttributes.zIndex = CGFLOAT_MAX;
            layoutAttributes.frame = (CGRect)
            {
                .origin = origin,
                .size = layoutAttributes.frame.size
            };

            break;
        }
    }

    return attributes;
}


- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBound
{
    return YES;
}


@end
Это было полезно?

Решение

I'm not 100% sure on this, but it looks like once you scrolled down far enough, the header's original position was no longer located inside the rect argument. This caused the header's layout attributes to not be included in the attributes array you iterated over in in the for loop, resulting in the layout position no longer being adjusted to its "sticky" position at the top of the screen.

Try adding these lines right before the for loop to add the sticky header's layout attributes to the attributes array if they are not already there:

NSIndexPath *stickyHeaderIndexPath = [NSIndexPath indexPathForItem:0 inSection:SectionWithStickyHeaderIndex];
UICollectionViewLayoutAttributes *layoutAttributes = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader
                                                                                          atIndexPath:stickyHeaderIndexPath];
if (![attributes containsObject:layoutAttributes])
{
    [attributes addObject:layoutAttributes];
}

Другие советы

The answer in Swift 5

let headerIndexPath = IndexPath(item: 0, section: 0)
if let headerAttributes = layoutAttributesForSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, at: headerIndexPath) {
    if !attributes.contains(headerAttributes) {
        attributes.append(headerAttributes)
    }
}

All thanks to @BrendanCain

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top