web-dev-qa-db-fra.com

UICollectionView avec un en-tête collant

J'ai trouvé un blog sur la façon de créer des en-têtes collants et cela fonctionne très bien. La seule chose est que je ne pense pas que cela prenne en compte la sectionInserts.

Voici à quoi cela devrait ressembler:

enter image description here

J'ai mes inserts:

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

Avec l'en-tête collant, il est abaissé de 16 pixels:

enter image description here

J'ai essayé de bricoler avec le code d'origine et je pense que le problème est avec la dernière partie:

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

Si je le change en Origin.y - 16, l'en-tête commencera au bon endroit, mais lorsqu'il est poussé vers le haut, 16 pixels de la tête disparaissent de l'écran:

enter image description here

Je ne sais pas comment l'obtenir pour prendre en compte la section Insectes. Quelqu'un peut-il aider?

Voici le code complet du 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;
}
28
Padin215

Correction par Todd Laney pour gérer le défilement horizontal et vertical et pour prendre en compte la section 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
33
rainerkohlberger

Solution la plus simple pour iOS 9 + car elle n'a pas besoin d'écrire la sous-classe de UICollectionViewFlowLayout.

Dans viewDidLoad de viewController avec collectionView, utilisez le code suivant:

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

C'est vraiment une bonne solution et fonctionne parfaitement. Cependant, puisque nous devons retourner OUI à partir de shouldINvalidateLayoutForBoundsChange, cela appelle fondamentalement prepareLayout chaque fois que la vue défile. Maintenant, SI votre prepareLayout a la responsabilité de créer les attributs de mise en page, ce qui est assez courant, cela affectera énormément les performances de défilement.

Une solution, qui a fonctionné pour moi, consiste à ne pas créer les attributs de mise en page dans prepareLayout mais plutôt à le faire dans une méthode distincte que vous appelez explicitement avant d'appeler invalidateLayout. Les appels UICollectionView prepareLayout au fur et à mesure qu'il estime avoir besoin de connaître la disposition et, par conséquent, cette solution prendra également en charge ces cas.

4
Deepak G M

Vous avez juste besoin de créer un nouveau UICollectionViewFlowLayout avec ce code:

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
        }
    }

}
2
David Beleza

Ce code fonctionne pour moi

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

Essayez ce gars ...

2
PSP

Rien de ce qui précède n'a fonctionné pour moi. Je cherchais une mise en page propre qui, en prenant soin de mes encarts, me donnait une collection de défilement de style Photo.app.

J'ai adapté la solution prosed ici pour prendre soin des paramètres edgeInsets. Pour plus de clarté, je joins ici la solution complète. Cependant, vous pouvez obtenir la solution complète à partir du Gist suivant: # 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
0
valvoline