In den Antworten auf diese Frage sind viele großartige Ideen enthalten. Die meisten von ihnen haben jedoch einige Nachteile:
- Lösungen, die den y- Wert der Zelle nicht überprüfen, funktionieren nur für einzeilige Layouts . Sie schlagen für Layouts von Sammlungsansichten mit mehreren Zeilen fehl.
- Lösungen, die den y- Wert überprüfen , wie die Antwort von Angel García Olloqui, funktionieren nur, wenn alle Zellen dieselbe Höhe haben . Sie schlagen für Zellen mit variabler Höhe fehl.
Die meisten Lösungen überschreiben nur die layoutAttributesForElements(in rect: CGRect)
Funktion. Sie überschreiben nicht layoutAttributesForItem(at indexPath: IndexPath)
. Dies ist ein Problem, da die Sammlungsansicht die letztere Funktion regelmäßig aufruft, um die Layoutattribute für einen bestimmten Indexpfad abzurufen. Wenn Sie von dieser Funktion nicht die richtigen Attribute zurückgeben, treten wahrscheinlich alle Arten von visuellen Fehlern auf, z. B. beim Einfügen und Löschen von Animationen von Zellen oder bei Verwendung von Zellen mit Selbstgröße durch Festlegen des Layouts der Sammlungsansicht estimatedItemSize
. In den Apple-Dokumenten heißt es :
Von jedem benutzerdefinierten Layoutobjekt wird erwartet, dass es die layoutAttributesForItemAtIndexPath:
Methode implementiert .
Viele Lösungen machen auch Annahmen über den rect
Parameter, der an die layoutAttributesForElements(in rect: CGRect)
Funktion übergeben wird. Zum Beispiel basieren viele auf der Annahme, dass das rect
immer am Anfang einer neuen Zeile beginnt, was nicht unbedingt der Fall ist.
Also mit anderen Worten:
Die meisten der auf dieser Seite vorgeschlagenen Lösungen funktionieren für bestimmte Anwendungen, funktionieren jedoch nicht in jeder Situation wie erwartet.
AlignedCollectionViewFlowLayout
Um diese Probleme anzugehen, habe ich eine UICollectionViewFlowLayout
Unterklasse erstellt, die einer ähnlichen Idee folgt, wie sie von Matt und Chris Wagner in ihren Antworten auf eine ähnliche Frage vorgeschlagen wurde. Es kann entweder die Zellen ausrichten
⬅︎ links :
oder ➡︎ richtig :
und bietet zusätzlich Optionen zum vertikalen Ausrichten der Zellen in ihren jeweiligen Zeilen (falls sie in der Höhe variieren).
Sie können es einfach hier herunterladen:
Die Verwendung ist unkompliziert und wird in der README-Datei erläutert. Sie erstellen im Grunde eine Instanz von AlignedCollectionViewFlowLayout
, geben die gewünschte Ausrichtung an und weisen sie der collectionViewLayout
Eigenschaft Ihrer Sammlungsansicht zu :
let alignedFlowLayout = AlignedCollectionViewFlowLayout(horizontalAlignment: .left,
verticalAlignment: .top)
yourCollectionView.collectionViewLayout = alignedFlowLayout
(Es ist auch auf Cocoapods erhältlich .)
So funktioniert es (für linksbündige Zellen):
Das Konzept hier besteht darin, sich ausschließlich auf die layoutAttributesForItem(at indexPath: IndexPath)
Funktion zu verlassen . In erhalten layoutAttributesForElements(in rect: CGRect)
wir einfach die Indexpfade aller Zellen innerhalb der rect
und rufen dann die erste Funktion für jeden Indexpfad auf, um die richtigen Frames abzurufen:
override public func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
// We may not change the original layout attributes
// or UICollectionViewFlowLayout might complain.
let layoutAttributesObjects = copy(super.layoutAttributesForElements(in: rect))
layoutAttributesObjects?.forEach({ (layoutAttributes) in
if layoutAttributes.representedElementCategory == .cell { // Do not modify header views etc.
let indexPath = layoutAttributes.indexPath
// Retrieve the correct frame from layoutAttributesForItem(at: indexPath):
if let newFrame = layoutAttributesForItem(at: indexPath)?.frame {
layoutAttributes.frame = newFrame
}
}
})
return layoutAttributesObjects
}
(Die copy()
Funktion erstellt einfach eine tiefe Kopie aller Layoutattribute im Array. Sie können den Quellcode für seine Implementierung untersuchen.)
Jetzt müssen wir nur noch die layoutAttributesForItem(at indexPath: IndexPath)
Funktion richtig implementieren . Die Superklasse fügt UICollectionViewFlowLayout
bereits die richtige Anzahl von Zellen in jede Zeile ein, sodass wir sie nur innerhalb ihrer jeweiligen Zeile nach links verschieben müssen. Die Schwierigkeit liegt in der Berechnung des Platzbedarfs, den wir benötigen, um jede Zelle nach links zu verschieben.
Da wir einen festen Abstand zwischen den Zellen haben möchten, besteht die Kernidee darin, einfach anzunehmen, dass die vorherige Zelle (die Zelle links von der aktuell angelegten Zelle) bereits richtig positioniert ist. Dann müssen wir nur den Zellenabstand zum maxX
Wert des Rahmens der vorherigen Zelle hinzufügen , und das ist der origin.x
Wert für den Rahmen der aktuellen Zelle.
Jetzt müssen wir nur noch wissen, wann wir den Anfang einer Zeile erreicht haben, damit wir keine Zelle neben einer Zelle in der vorherigen Zeile ausrichten. (Dies würde nicht nur zu einem falschen Layout führen, sondern auch extrem verzögert.) Wir benötigen also einen Rekursionsanker. Der Ansatz, mit dem ich diesen Rekursionsanker finde, ist der folgende:
Um herauszufinden, ob sich die Zelle am Index i in derselben Zeile befindet wie die Zelle mit dem Index i-1 ...
+---------+----------------------------------------------------------------+---------+
| | | |
| | +------------+ | |
| | | | | |
| section |- - -|- - - - - - |- - - - +---------------------+ - - - - - - -| section |
| inset | |intersection| | | line rect | inset |
| |- - -|- - - - - - |- - - - +---------------------+ - - - - - - -| |
| (left) | | | current item | (right) |
| | +------------+ | |
| | previous item | |
+---------+----------------------------------------------------------------+---------+
... Ich "zeichne" ein Rechteck um die aktuelle Zelle und strecke es über die Breite der gesamten Sammlungsansicht. Da die UICollectionViewFlowLayout
Zentren alle Zellen vertikal zentrieren, muss sich jede Zelle in derselben Linie mit diesem Rechteck schneiden.
Daher überprüfe ich einfach, ob sich die Zelle mit dem Index i-1 mit diesem Linienrechteck schneidet, das aus der Zelle mit dem Index i erstellt wurde .
Wenn es sich schneidet, ist die Zelle mit dem Index i nicht die Zelle ganz links in der Zeile.
→ Holen Sie sich den Frame der vorherigen Zelle (mit dem Index i - 1 ) und verschieben Sie die aktuelle Zelle daneben.
Wenn es sich nicht schneidet, ist die Zelle mit dem Index i die Zelle ganz links in der Zeile.
→ Bewegen Sie die Zelle an den linken Rand der Sammlungsansicht (ohne ihre vertikale Position zu ändern).
Ich werde die tatsächliche Implementierung der layoutAttributesForItem(at indexPath: IndexPath)
Funktion hier nicht veröffentlichen, da ich denke, dass der wichtigste Teil darin besteht, die Idee zu verstehen , und Sie meine Implementierung immer im Quellcode überprüfen können . (Es ist etwas komplizierter als hier erklärt, da ich auch die .right
Ausrichtung und verschiedene vertikale Ausrichtungsoptionen zulasse . Es folgt jedoch der gleichen Idee.)
Wow, ich denke, dies ist die längste Antwort, die ich jemals auf Stackoverflow geschrieben habe. Ich hoffe das hilft. 😉