2013-03-05 3 views
20

I found a blog on how to make sticky headers e funziona benissimo. L'unica cosa è che non penso che tenga conto della sezioneInserti.UICollectionView con un'intestazione appiccicosa

Questo è come la sua destinato a guardare:

enter image description here

ho i miei inserti:

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

con l'intestazione appiccicoso, viene spostato verso il basso da 16 pixles:

enter image description here

Ho provato lo stagno re con il codice originale e penso che il problema è con l'ultima parte:

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

Se cambio al origin.y - 16, l'intestazione inizierà nella giusta posizione, ma quando spinto verso l'alto, 16 pixel della testa si spengono schermo:

enter image description here

io non sono sicuro di come ottenere di prendere in considerazione sectionInsects. Qualcuno può aiutare?

Ecco il codice per intero dal 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; 
} 
+0

mai questo numero? Ho lo stesso problema al momento. – elsurudo

+0

No. Guardando a finta con un'intestazione trasparente. – Padin215

+0

Il collegamento al blog è molto utile, grazie per aver condiviso il riferimento –

risposta

31

Fix da Todd Laney per gestire orizzontale e verticale lo scorrimento e per tener conto delle sectionInsets:

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

Si dovrebbe anche rispettare 'contentInset' per il calcolo del' frame', ad es. 'frame.origin.y = MIN (MAX (contentOffset.y + cv.contentInset.top, frame.origin.y), nextHeaderOrigin.y - CGRectGetHeight (frame)); '. È probabile che in iOS7 non siano 0 e quindi la tua intestazione rimarrà sotto la barra di navigazione. –

+0

Si noti inoltre che l'intestazione non verrà visualizzata quando una sezione ha 0 elementi, mentre lo spazio per esso continua a essere visualizzato. Potresti scegliere di non mostrare lo spazio per esso, ma in alternativa puoi semplicemente mostrare l'intestazione anche quando la sezione contiene 0 elementi. In questo caso dovresti anche aggiungere quelle sezioni al '' 'missingSections''' set per tutte le intestazioni che incontri nella' '' answer''' iniziale. – MrJre

+0

@ MrJe - come risolveresti il ​​caso in cui una sezione non ha righe? Non sono in grado di incorporare il tuo commento, purtroppo ... grazie! – trdavidson

2

Questa è davvero una buona soluzione e funziona perfettamente. Tuttavia, dal momento che dobbiamo restituire YES da shouldINvalidateLayoutForBoundsChange, questo in pratica chiama prepareLayout ogni volta che scorre la vista. Ora, SE, il tuo prepareLayout ha la responsabilità di creare gli attributi di layout, che è abbastanza comune, questo influenzerà immensamente le prestazioni di scorrimento.

Una soluzione, che ha funzionato per me, è quella di non creare gli attributi di layout in prepareLayout, ma invece di farlo in un metodo separato che chiami esplicitamente prima di chiamare invalidateLayout. Le chiamate UICollectionView preparano il layout come e quando ritiene necessario conoscere il layout e quindi questa soluzione si occuperà anche di questi casi.

2

Questo codice funziona per me

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

provare questo ragazzi ...

0

Nessuno dei precedenti ha lavorato per me. Stavo cercando un layout pulito che, avendo cura dei miei riquadri, mi dia una collezione a scorrimento in stile Photo.app.

Ho adattato la soluzione al numero here per gestire le impostazioni di EdgeInsets. Per chiarezza, allego la soluzione completa qui. Tuttavia, è possibile ottenere la soluzione completa dai seguenti aspetti: #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

Questo non funziona quando la prima sezione non ha righe. Creerà lo spazio dell'intestazione della sezione sotto l'intestazione della sezione. – trdavidson

57

soluzione più semplice per iOS 9 + in quanto non ha bisogno di scrivere sottoclasse di UICollectionViewFlowLayout.

In viewDidLoad di viewController con l'utilizzo CollectionView seguente codice:

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

soluzione assolutamente perfetta quando si utilizza un layout di flusso :) – luk2302