Ich kämpfe darum, einen "Floating Section Header" -Effekt zu erzielen UICollectionView
. Etwas, das einfach genug war UITableView
(Standardverhalten für UITableViewStylePlain
), scheint UICollectionView
ohne viel harte Arbeit unmöglich zu sein. Vermisse ich das Offensichtliche?
Apple bietet keine Dokumentation dazu an. Es scheint, dass man UICollectionViewLayout
ein benutzerdefiniertes Layout unterordnen und implementieren muss, um diesen Effekt zu erzielen. Dies erfordert viel Arbeit bei der Implementierung der folgenden Methoden:
Methoden zum Überschreiben
Jedes Layoutobjekt sollte die folgenden Methoden implementieren:
collectionViewContentSize
layoutAttributesForElementsInRect:
layoutAttributesForItemAtIndexPath:
layoutAttributesForSupplementaryViewOfKind:atIndexPath: (if your layout supports supplementary views)
layoutAttributesForDecorationViewOfKind:atIndexPath: (if your layout supports decoration views)
shouldInvalidateLayoutForBoundsChange:
Mir ist jedoch nicht klar, wie ich die Zusatzansicht über die Zellen schweben lassen und am oberen Rand der Ansicht "bleiben" kann, bis der nächste Abschnitt erreicht ist. Gibt es dafür ein Flag in den Layoutattributen?
Ich hätte verwendet, UITableView
aber ich muss eine ziemlich komplexe Hierarchie von Sammlungen erstellen, die mit einer Sammlungsansicht leicht erreicht werden kann.
Jede Anleitung oder Beispielcode wäre sehr dankbar!
quelle
yourCollectionView.collectionViewLayout
UICollectionViewFlowLayout
(self.yourCollectionView.collectionViewLayout as! UICollectionViewFlowLayout).sectionHeadersPinToVisibleBounds = true
Implementieren Sie entweder die folgenden Delegatenmethoden:
– collectionView:layout:sizeForItemAtIndexPath: – collectionView:layout:insetForSectionAtIndex: – collectionView:layout:minimumLineSpacingForSectionAtIndex: – collectionView:layout:minimumInteritemSpacingForSectionAtIndex: – collectionView:layout:referenceSizeForHeaderInSection: – collectionView:layout:referenceSizeForFooterInSection:
In Ihrem View Controller, der Ihre
:cellForItemAtIndexPath
Methode hat (geben Sie einfach die richtigen Werte zurück). Anstatt die Delegatmethoden zu verwenden, können Sie diese Werte auch direkt in Ihrem Layoutobjekt festlegen, z[layout setItemSize:size];
.Mit einer dieser Methoden können Sie Ihre Einstellungen in Code und nicht in IB festlegen, da diese beim Festlegen eines benutzerdefinierten Layouts entfernt werden. Denken Sie daran, auch
<UICollectionViewDelegateFlowLayout>
zu Ihrer .h-Datei hinzuzufügen !Erstellen Sie eine neue Unterklasse von
UICollectionViewFlowLayout
, nennen Sie sie wie gewünscht und stellen Sie sicher, dass die H-Datei Folgendes enthält:#import <UIKit/UIKit.h> @interface YourSubclassNameHere : UICollectionViewFlowLayout @end
Stellen Sie in der Implementierungsdatei Folgendes sicher:
- (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]; NSIndexPath *firstObjectIndexPath = [NSIndexPath indexPathForItem:0 inSection:section]; NSIndexPath *lastObjectIndexPath = [NSIndexPath indexPathForItem:MAX(0, (numberOfItemsInSection - 1)) inSection:section]; UICollectionViewLayoutAttributes *firstObjectAttrs; UICollectionViewLayoutAttributes *lastObjectAttrs; if (numberOfItemsInSection > 0) { firstObjectAttrs = [self layoutAttributesForItemAtIndexPath:firstObjectIndexPath]; lastObjectAttrs = [self layoutAttributesForItemAtIndexPath:lastObjectIndexPath]; } else { firstObjectAttrs = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader atIndexPath:firstObjectIndexPath]; lastObjectAttrs = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionFooter atIndexPath:lastObjectIndexPath]; } CGFloat headerHeight = CGRectGetHeight(layoutAttributes.frame); CGPoint origin = layoutAttributes.frame.origin; origin.y = MIN( MAX( contentOffset.y + cv.contentInset.top, (CGRectGetMinY(firstObjectAttrs.frame) - headerHeight) ), (CGRectGetMaxY(lastObjectAttrs.frame) - headerHeight) ); layoutAttributes.zIndex = 1024; layoutAttributes.frame = (CGRect){ .origin = origin, .size = layoutAttributes.frame.size }; } } return answer; } - (BOOL) shouldInvalidateLayoutForBoundsChange:(CGRect)newBound { return YES; }
Wählen Sie im Interface Builder "Benutzerdefiniert" für das Ablauflayout und anschließend die soeben erstellte Klasse "YourSubclassNameHere". Und Renn!
(Hinweis: Der obige Code berücksichtigt möglicherweise nicht die Werte von contentInset.bottom oder insbesondere große oder kleine Fußzeilenobjekte oder Sammlungen mit 0 Objekten, aber ohne Fußzeile.)
quelle
numberOfItemsInSection == 0
, erhalten Sie einenEXC_ARITHMETIC code=EXC_I386_DIV
(durch Null dividieren?) Absturz in dieser Zeile :UICollectionViewLayoutAttributes *firstCellAttrs = [self layoutAttributesForItemAtIndexPath:firstCellIndexPath];
. Ich habe auch festgestellt, dass diecontentOffset
Teile dieses Codes den obersten Wert der Sammlungsansicht in meiner Implementierung nicht berücksichtigen (möglicherweise im Zusammenhang mit der vorherigen Ausgabe). Schlägt eine Codeänderung / gist / etc. Vor.collectionView:layout:sizeForItemAtIndexPath:
. Insbesondere beträgt der Inhalt der inneren Zelle 50 x 50, aber die Größe der Zelle ist immer noch korrekt. Tritt nur beim Erstellen mit iOS 8 SDK auf, läuft jedoch unter iOS 7.0.X. Das Erstellen gegen iOS 7.1 SDK und das Ausführen unter 7.0.X funktioniert einwandfrei.Wenn Sie eine einzelne Header-Ansicht haben, die oben in Ihrer UICollectionView angeheftet werden soll, ist dies eine relativ einfache Möglichkeit. Beachten Sie, dass dies so einfach wie möglich sein soll - es wird davon ausgegangen, dass Sie einen einzelnen Header in einem einzelnen Abschnitt verwenden.
//Override UICollectionViewFlowLayout class @interface FixedHeaderLayout : UICollectionViewFlowLayout @end @implementation FixedHeaderLayout //Override shouldInvalidateLayoutForBoundsChange to require a layout update when we scroll - (BOOL) shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds { return YES; } //Override layoutAttributesForElementsInRect to provide layout attributes with a fixed origin for the header - (NSArray *) layoutAttributesForElementsInRect:(CGRect)rect { NSMutableArray *result = [[super layoutAttributesForElementsInRect:rect] mutableCopy]; //see if there's already a header attributes object in the results; if so, remove it NSArray *attrKinds = [result valueForKeyPath:@"representedElementKind"]; NSUInteger headerIndex = [attrKinds indexOfObject:UICollectionElementKindSectionHeader]; if (headerIndex != NSNotFound) { [result removeObjectAtIndex:headerIndex]; } CGPoint const contentOffset = self.collectionView.contentOffset; CGSize headerSize = self.headerReferenceSize; //create new layout attributes for header UICollectionViewLayoutAttributes *newHeaderAttributes = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader withIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]; CGRect frame = CGRectMake(0, contentOffset.y, headerSize.width, headerSize.height); //offset y by the amount scrolled newHeaderAttributes.frame = frame; newHeaderAttributes.zIndex = 1024; [result addObject:newHeaderAttributes]; return result; } @end
Siehe: https://gist.github.com/4613982
quelle
Hier ist meine Einstellung dazu, ich denke, es ist viel einfacher als das, was man oben gesehen hat. Die Hauptquelle der Einfachheit ist, dass ich das Flow-Layout nicht unterklassifiziere, sondern mein eigenes Layout rolle (viel einfacher, wenn Sie mich fragen).
Bitte beachten Sie, dass ich davon ausgehe, dass Sie bereits in der Lage sind, Ihre eigenen benutzerdefinierten
UICollectionViewLayout
Elemente zu implementieren, die Zellen und Header anzeigen, ohne dass Floating implementiert ist. Sobald Sie diese Implementierung geschrieben haben, ist der folgende Code nur dann sinnvoll. Dies liegt wiederum daran, dass das OP speziell nach dem schwebenden Header-Teil gefragt hat.ein paar Boni:
Hinweis:
supplementaryLayoutAttributes
enthält alle Header-Attribute ohne Floating implementiertprepareLayout
, da ich alle Berechnungen im Voraus durchführe.shouldInvalidateLayoutForBoundsChange
auf wahr zu setzen !// float them headers let yOffset = self.collectionView!.bounds.minY let headersRect = CGRect(x: 0, y: yOffset, width: width, height: headersHeight) var floatingAttributes = supplementaryLayoutAttributes.filter { $0.frame.minY < headersRect.maxY } // This is three, because I am floating 2 headers // so 2 + 1 extra that will be pushed away var index = 3 var floatingPoint = yOffset + dateHeaderHeight while index-- > 0 && !floatingAttributes.isEmpty { let attribute = floatingAttributes.removeLast() attribute.frame.origin.y = max(floatingPoint, attribute.frame.origin.y) floatingPoint = attribute.frame.minY - dateHeaderHeight }
quelle
Wenn Sie bereits das Flow-Layout in
Storyboard
oder dieXib
Datei festgelegt haben, versuchen Sie Folgendes:(collectionView.collectionViewLayout as? UICollectionViewFlowLayout)?.sectionHeadersPinToVisibleBounds = true
quelle
Falls jemand in Objective-C nach einer Lösung sucht, fügen Sie diese in viewDidload ein:
UICollectionViewFlowLayout *flowLayout = (UICollectionViewFlowLayout*)_yourcollectionView.collectionViewLayout; [flowLayout setSectionHeadersPinToVisibleBounds:YES];
quelle
@iPrabu hatte eine ausgezeichnete Antwort mit
sectionHeadersPinToVisibleBounds
. Ich möchte nur hinzufügen, dass Sie diese Eigenschaft auch im Interface Builder festlegen können:sectionHeadersPinToVisibleBounds
, Typ Boolean und aktiviertem Kontrollkästchen hinzu.Die Standard-Header-Ansicht hat einen transparenten Hintergrund. Möglicherweise möchten Sie es (teilweise) undurchsichtig machen oder eine Unschärfeeffektansicht hinzufügen.
quelle
Ich bin auf dasselbe Problem gestoßen und habe es in meinen Google-Ergebnissen gefunden. Zunächst möchte ich Cocotutch für das Teilen seiner Lösung danken. Ich wollte jedoch, dass meine UICollectionView horizontal scrollt und die Überschriften links vom Bildschirm angezeigt werden, sodass ich die Lösung etwas ändern musste.
Grundsätzlich habe ich das geändert:
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 };
dazu:
if (self.scrollDirection == UICollectionViewScrollDirectionVertical) { 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 }; } else { CGFloat headerWidth = CGRectGetWidth(layoutAttributes.frame); CGPoint origin = layoutAttributes.frame.origin; origin.x = MIN( MAX(contentOffset.x, (CGRectGetMinX(firstCellAttrs.frame) - headerWidth)), (CGRectGetMaxX(lastCellAttrs.frame) - headerWidth) ); layoutAttributes.zIndex = 1024; layoutAttributes.frame = (CGRect){ .origin = origin, .size = layoutAttributes.frame.size }; }
Siehe: https://gist.github.com/vigorouscoding/5155703 oder http://www.vigorouscoding.com/2013/03/uicollectionview-with-sticky-headers/
quelle
Ich habe dies mit dem Code von vigorouscoding ausgeführt . Dieser Code berücksichtigte jedoch nicht sectionInset.
Also habe ich diesen Code für vertikales Scrollen geändert
origin.y = MIN( MAX(contentOffset.y, (CGRectGetMinY(firstCellAttrs.frame) - headerHeight)), (CGRectGetMaxY(lastCellAttrs.frame) - headerHeight) );
zu
origin.y = MIN( MAX(contentOffset.y, (CGRectGetMinY(firstCellAttrs.frame) - headerHeight - self.sectionInset.top)), (CGRectGetMaxY(lastCellAttrs.frame) - headerHeight + self.sectionInset.bottom) );
Wenn Sie Code für horizontales Scrollen möchten, lesen Sie den obigen Code.
quelle
Ich habe ein Beispiel für Github hinzugefügt , das ziemlich einfach ist, denke ich.
Grundsätzlich besteht die Strategie darin, ein benutzerdefiniertes Layout bereitzustellen, das bei Änderungen der Grenzen ungültig wird, und Layoutattribute für die Zusatzansicht bereitzustellen, die die aktuellen Grenzen umfassen. Wie andere vorgeschlagen haben. Ich hoffe der Code ist nützlich.
quelle
Es gibt einen Fehler in Cocotouchs Beitrag. Wenn der Abschnitt keine Elemente enthält und die Fußzeile des Abschnitts nicht angezeigt wurde, wird die Abschnittsüberschrift außerhalb der Sammlungsansicht angezeigt und der Benutzer kann sie nicht sehen.
In der Tat ändern:
if (numberOfItemsInSection > 0) { firstObjectAttrs = [self layoutAttributesForItemAtIndexPath:firstObjectIndexPath]; lastObjectAttrs = [self layoutAttributesForItemAtIndexPath:lastObjectIndexPath]; } else { firstObjectAttrs = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader atIndexPath:firstObjectIndexPath]; lastObjectAttrs = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionFooter atIndexPath:lastObjectIndexPath]; }
in:
if (numberOfItemsInSection > 0) { firstObjectAttrs = [self layoutAttributesForItemAtIndexPath:firstObjectIndexPath]; lastObjectAttrs = [self layoutAttributesForItemAtIndexPath:lastObjectIndexPath]; } else { firstObjectAttrs = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader atIndexPath:firstObjectIndexPath]; lastObjectAttrs = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionFooter atIndexPath:lastObjectIndexPath]; if (lastObjectAttrs == nil) { lastObjectAttrs = firstObjectAttrs; } }
wird dieses Problem lösen.
quelle
VCollectionViewGridLayout erstellt klebrige Header. Es ist ein einfaches Rasterlayout mit vertikalem Bildlauf, das auf TLIndexPathTools basiert . Versuchen Sie, das Beispielprojekt Sticky Headers auszuführen .
Dieses Layout hat auch ein viel besseres Batch-Update-Animationsverhalten als
UICollectionViewFlowLayout
. Es gibt einige Beispielprojekte, mit denen Sie zwischen den beiden Layouts wechseln können, um die Verbesserung zu demonstrieren.quelle
VCollectionViewGridLayoutDelegate
die Delegatmethoden implementieren müssen , für die selbst kein TLIPT erforderlich ist. Die Hauptabsicht dieser Bibliothek bestand jedoch darin, einige Animationsprobleme mit dem Ablauflayout zu umgehen. Die Funktion für klebrige Überschriften ist zweitrangig, daher gibt es möglicherweise bessere Optionen für diejenigen, die nur an klebrigen Überschriften interessiert sind.Swift 5.0
Fügen Sie Folgendes in Ihre viewDidLoad ein:
if let layout = collectionView?.collectionViewLayout as? UICollectionViewFlowLayout { layout.sectionHeadersPinToVisibleBounds = true }
quelle
(collectionView.collectionViewLayout as? UICollectionViewFlowLayout)?.sectionHeadersPinToVisibleBounds = true