Подтвердить что ты не робот

UICollectionView с липким заголовком

Я нашел блог о том, как делать липкие заголовки, и он отлично работает. Единственное, что я не думаю, что он принимает во внимание разделInserts.

Вот как это должно выглядеть:

enter image description here

У меня есть мои вставки:

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

С липким заголовком он перемещается на 16 пикселей:

enter image description here

Я пробовал общаться с исходным кодом, и я думаю, что проблема связана с последней частью:

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

Если я изменю его на origin.y - 16, заголовок начнется в нужном месте, но при нажатии на него на экране исчезнет 16 пикселей головы:

enter image description here

Я не уверен, как заставить его учитывать разделInsects. Может ли кто-нибудь помочь?

Вот полный код из блога:

- (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;
}
4b9b3361

Ответ 1

Исправьте Тодда Лани для обработки горизонтальной и вертикальной прокрутки и учесть разделInsets:

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

Ответ 2

Самое простое решение для iOS 9 +, поскольку ему не требуется писать подкласс UICollectionViewFlowLayout.

В viewDidLoad viewController с помощью коллекцииView используйте следующий код:

let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout // casting is required because UICollectionViewLayout doesn't offer header pin. Its feature of UICollectionViewFlowLayout
layout?.sectionHeadersPinToVisibleBounds = true

Ответ 3

Это действительно хорошее решение и прекрасно работает. Однако, поскольку мы должны вернуть YES из mustINvalidateLayoutForBoundsChange, это в основном вызывает prepareLayout каждый раз, когда свиток открывается. Теперь IF ваша подготовкаLayout несет ответственность за создание атрибутов макета, что довольно часто, это существенно повлияет на производительность прокрутки.

Одно из решений, которое сработало для меня, заключается в том, чтобы не создавать атрибуты макета в prepareLayout, а вместо этого делать это в отдельном методе, который вы вызываете явно перед вызовом invalidateLayout. UICollectionView вызывает prepareLayout, когда и когда он чувствует, что ему нужно знать о макете, и поэтому это решение также позаботится об этих случаях.

Ответ 4

Этот код работает для меня

    -(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
        NSMutableArray *answer = [[super layoutAttributesForElementsInRect:rect] mutableCopy];
        UICollectionView * const cv = self.collectionView;
        //CLS_LOG(@"Number of sections = %d", [cv numberOfSections]);
        CGPoint const contentOffset = cv.contentOffset;

    //CLS_LOG(@"Adding missing sections");
    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];

    }];

    NSInteger numberOfSections = [cv numberOfSections];

    //CLS_LOG(@"For loop");
    for (UICollectionViewLayoutAttributes *layoutAttributes in answer) {

        if ([layoutAttributes.representedElementKind isEqualToString:UICollectionElementKindSectionHeader]) {

            NSInteger section = layoutAttributes.indexPath.section;
            //CLS_LOG(@"Customizing layout attribute for header in section %d with number of items = %d", section, [cv numberOfItemsInSection:section]);

            if (section < numberOfSections) {
                NSInteger numberOfItemsInSection = [cv numberOfItemsInSection:section];

                NSIndexPath *firstObjectIndexPath = [NSIndexPath indexPathForItem:0 inSection:section];
                NSIndexPath *lastObjectIndexPath = [NSIndexPath indexPathForItem:MAX(0, (numberOfItemsInSection - 1)) inSection:section];

                BOOL cellsExist;
                UICollectionViewLayoutAttributes *firstObjectAttrs;
                UICollectionViewLayoutAttributes *lastObjectAttrs;

                if (numberOfItemsInSection > 0) { // use cell data if items exist
                    cellsExist = YES;
                    firstObjectAttrs = [self layoutAttributesForItemAtIndexPath:firstObjectIndexPath];
                    lastObjectAttrs = [self layoutAttributesForItemAtIndexPath:lastObjectIndexPath];
                } else { // else use the header and footer
                    cellsExist = NO;
                    firstObjectAttrs = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader
                                                                            atIndexPath:firstObjectIndexPath];
                    lastObjectAttrs = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionFooter
                                                                           atIndexPath:lastObjectIndexPath];

                }

                CGFloat topHeaderHeight = (cellsExist) ? CGRectGetHeight(layoutAttributes.frame) : 0;
                CGFloat bottomHeaderHeight = CGRectGetHeight(layoutAttributes.frame);
                CGRect frameWithEdgeInsets = UIEdgeInsetsInsetRect(layoutAttributes.frame,
                                                                   cv.contentInset);

                CGPoint origin = frameWithEdgeInsets.origin;

                origin.y = MIN(
                               MAX(
                                   contentOffset.y + cv.contentInset.top,
                                   (CGRectGetMinY(firstObjectAttrs.frame) - topHeaderHeight)
                                   ),
                               (CGRectGetMaxY(lastObjectAttrs.frame) - bottomHeaderHeight)
                               );

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

    }

    return answer;

}

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

Попробуйте этих парней...

Ответ 5

Вам просто нужно создать новый UICollectionViewFlowLayout с этим кодом:

class StickyHeaderLayout: UICollectionViewFlowLayout {

    override init() {
        super.init()
        self.sectionFootersPinToVisibleBounds = true
        self.sectionHeadersPinToVisibleBounds = true
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        self.sectionFootersPinToVisibleBounds = true
        self.sectionHeadersPinToVisibleBounds = true
    }

    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        guard let attributes = super.layoutAttributesForElements(in: rect) else { return nil }

        for attribute in attributes {
            adjustAttributesIfNeeded(attribute)
        }
        return attributes
    }

    override func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        guard let attributes = super.layoutAttributesForSupplementaryView(ofKind: elementKind, at: indexPath) else { return nil }
        adjustAttributesIfNeeded(attributes)
        return attributes
    }

    func adjustAttributesIfNeeded(_ attributes: UICollectionViewLayoutAttributes) {
        switch attributes.representedElementKind {
        case UICollectionElementKindSectionHeader?:
            adjustHeaderAttributesIfNeeded(attributes)
        case UICollectionElementKindSectionFooter?:
            adjustFooterAttributesIfNeeded(attributes)
        default:
            break
        }
    }

    private func adjustHeaderAttributesIfNeeded(_ attributes: UICollectionViewLayoutAttributes) {
        guard let collectionView = collectionView else { return }
        guard attributes.indexPath.section == 0 else { return }

        if collectionView.contentOffset.y < 0 {
            attributes.frame.origin.y = collectionView.contentOffset.y
        }
    }

    private func adjustFooterAttributesIfNeeded(_ attributes: UICollectionViewLayoutAttributes) {
        guard let collectionView = collectionView else { return }
        guard attributes.indexPath.section == collectionView.numberOfSections - 1 else { return }

        if collectionView.contentOffset.y + collectionView.bounds.size.height > collectionView.contentSize.height {
            attributes.frame.origin.y = collectionView.contentOffset.y + collectionView.bounds.size.height - attributes.frame.size.height
        }
    }

}

Ответ 6

Ничто из этого не помогло мне. Я искал чистую макет, которая, заботясь о моих вставках, дала мне прокручиваемую коллекцию стиля Photo.app.

Я адаптировал решение, которое пропустило здесь, чтобы позаботиться о настройках edgeInsets. Для ясности я приложу здесь полное решение. Однако вы можете получить полное решение из следующего списка: # 3e1955a4492a897e677f.

@implementation SpringboardLayout

- (id)init
{
    if (self = [super init])
    {
        self.headerReferenceSize = CGSizeMake(0, 50);
        self.footerReferenceSize = CGSizeMake(0, 0);
        self.sectionInset = UIEdgeInsetsMake(10, 10, 80, 10);
        self.scrollDirection = UICollectionViewScrollDirectionVertical;
        self.minimumInteritemSpacing = 10;
        self.minimumLineSpacing = 10;
        if(IS_IPHONE_6 || IS_IPHONE_6PLUS) {
            self.itemSize = CGSizeMake(100, 128);
        } else {
            self.itemSize = CGSizeMake(80, 108);
        }
    }
    return self;
}

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

- (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];
        } else 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 *firstObjectIndexPath = [NSIndexPath indexPathForItem:0 inSection:section];
            NSIndexPath *lastObjectIndexPath = [NSIndexPath indexPathForItem:MAX(0, (numberOfItemsInSection - 1)) inSection:section];

            BOOL cellsExist;
            UICollectionViewLayoutAttributes *firstObjectAttrs;
            UICollectionViewLayoutAttributes *lastObjectAttrs;

            if (numberOfItemsInSection > 0) { // use cell data if items exist
                cellsExist = YES;
                firstObjectAttrs = [self layoutAttributesForItemAtIndexPath:firstObjectIndexPath];
                lastObjectAttrs = [self layoutAttributesForItemAtIndexPath:lastObjectIndexPath];
            } else { // else use the header and footer
                cellsExist = NO;
                firstObjectAttrs = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader
                                                                        atIndexPath:firstObjectIndexPath];
                lastObjectAttrs = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionFooter
                                                                       atIndexPath:lastObjectIndexPath];

            }

            CGFloat topHeaderHeight = (cellsExist) ? CGRectGetHeight(layoutAttributes.frame) : 0;
            CGFloat bottomHeaderHeight = CGRectGetHeight(layoutAttributes.frame);
            CGRect frameWithEdgeInsets = UIEdgeInsetsInsetRect(layoutAttributes.frame,
                                                               cv.contentInset);

            CGPoint origin = frameWithEdgeInsets.origin;


            origin.y = MIN(MAX(contentOffset.y + cv.contentInset.top,
                               (CGRectGetMinY(firstObjectAttrs.frame) - topHeaderHeight - self.sectionInset.top))
                           ,(CGRectGetMaxY(lastObjectAttrs.frame) - bottomHeaderHeight + self.sectionInset.bottom));
            layoutAttributes.zIndex = 1024;
            layoutAttributes.frame = (CGRect){
                .origin = origin,
                .size = layoutAttributes.frame.size
            };
        }
    }
    return answer;
}

@end