wake-up-neo.com

Linke Zellen in UICollectionView ausrichten

In meinem Projekt verwende ich eine UICollectionView, in der sich in einer Zeile mehrere Zellen unterschiedlicher Breite befinden. Gemäß: https://developer.Apple.com/library/content/documentation/WindowsViews/Conceptual/CollectionViewPGforIOS/UsingtheFlowLayout/UsingtheFlowLayout.html

es verteilt die Zellen mit gleicher Polsterung über die Linie. Dies geschieht erwartungsgemäß, mit der Ausnahme, dass ich sie linksbündig ausrichten und eine Füllbreite hart codieren möchte.

Ich denke, ich muss UICollectionViewFlowLayout subclass, aber nachdem ich einige der Tutorials usw. online gelesen habe, scheint mir einfach nicht klar zu sein, wie das funktioniert.

62
Damien

Die anderen Lösungen hier funktionieren nicht richtig, wenn die Zeile nur aus einem Artikel besteht oder zu kompliziert ist.

Basierend auf dem von Ryan gegebenen Beispiel habe ich den Code geändert, um eine neue Zeile zu erkennen, indem ich die Y-Position des neuen Elements überprüfe. Sehr einfach und schnell in der Leistung.

Schnell: 

class LeftAlignedCollectionViewFlowLayout: UICollectionViewFlowLayout {

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

        var leftMargin = sectionInset.left
        var maxY: CGFloat = -1.0
        attributes?.forEach { layoutAttribute in
            if layoutAttribute.frame.Origin.y >= maxY {
                leftMargin = sectionInset.left
            }

            layoutAttribute.frame.Origin.x = leftMargin

            leftMargin += layoutAttribute.frame.width + minimumInteritemSpacing
            maxY = max(layoutAttribute.frame.maxY , maxY)
        }

        return attributes
    }
}

Wenn Sie möchten, dass Zusatzansichten ihre Größe beibehalten, fügen Sie im Aufruf forEach oben am Abschluss Folgendes hinzu:

guard layoutAttribute.representedElementCategory == .cell else {
    return
}

Ziel c: 

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
    NSArray *attributes = [super layoutAttributesForElementsInRect:rect];

    CGFloat leftMargin = self.sectionInset.left; //initalized to silence compiler, and actaully safer, but not planning to use.
    CGFloat maxY = -1.0f;

    //this loop assumes attributes are in IndexPath order
    for (UICollectionViewLayoutAttributes *attribute in attributes) {
        if (attribute.frame.Origin.y >= maxY) {
            leftMargin = self.sectionInset.left;
        }

        attribute.frame = CGRectMake(leftMargin, attribute.frame.Origin.y, attribute.frame.size.width, attribute.frame.size.height);

        leftMargin += attribute.frame.size.width + self.minimumInteritemSpacing;
        maxY = MAX(CGRectGetMaxY(attribute.frame), maxY);
    }

    return attributes;
}
105

Die Antworten auf diese Frage enthalten viele großartige Ideen. Die meisten von ihnen haben jedoch einige Nachteile:

  • Lösungen, bei denen der Wert y der Zelle nicht überprüft wird, funktionieren nur für einzeilige Layouts . Sie schlagen bei Layouts der Sammlungsansicht mit mehreren Zeilen fehl.
  • Lösungen, die do den y -Wert wie Angel García Olloquis Antwort überprüfen, funktionieren nur, wenn alle Zellen den gleichen Wert haben Höhe . Sie schlagen bei Zellen mit variabler Höhe fehl.
  • Die meisten Lösungen überschreiben nur die Funktion layoutAttributesForElements(in rect: CGRect). Sie überschreiben nicht layoutAttributesForItem(at indexPath: IndexPath). Dies ist ein Problem, da die Sammlungsansicht regelmäßig die letztere Funktion 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. beim Einfügen und Löschen von Animationen von Zellen oder bei der Verwendung von Zellen mit automatischer Größenänderung durch Festlegen des estimatedItemSizedes Layouts der Sammlungsansicht. Das Apple docs lautet:

    Von jedem benutzerdefinierten Layoutobjekt wird erwartet, dass es die Methode layoutAttributesForItemAtIndexPath: implementiert.

  • Bei vielen Lösungen werden auch Annahmen zum Parameter rectgetroffen, der an die Funktion layoutAttributesForElements(in rect: CGRect) übergeben wird. Zum Beispiel basieren viele auf der Annahme, dass rectimmer 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, jedoch nicht in jeder Situation wie erwartet.


AlignedCollectionViewFlowLayout

Um diese Probleme anzugehen, habe ich eine Unterklasse UICollectionViewFlowLayouterstellt, die einer ähnlichen Idee folgt, wie sie von matt und Chris Wagner vorgeschlagen wurde in ihren Antworten auf eine ähnliche Frage. Es kann entweder die Zellen ausrichten

⬅︎  übrig :

Left-aligned layout

oder ➡︎  richtig :

Right-aligned layout

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:

https://github.com/mischa-hildebrand/AlignedCollectionViewFlowLayout

Die Verwendung ist unkompliziert und wird in der Datei README erläutert. Grundsätzlich erstellen Sie eine Instanz von AlignedCollectionViewFlowLayoutname__, geben die gewünschte Ausrichtung an und weisen sie der Eigenschaft collectionViewLayoutIhrer Sammlungsansicht zu:

 let alignedFlowLayout = AlignedCollectionViewFlowLayout(horizontalAlignment: .left, 
                                                         verticalAlignment: .top)

 yourCollectionView.collectionViewLayout = alignedFlowLayout

(Es ist auch verfügbar auf Cocoapods .)


So funktioniert es (bei linksbündigen Zellen):

Das Konzept hier ist, sich ausschließlich auf die Funktion layoutAttributesForItem(at indexPath: IndexPath) zu verlassen . In der layoutAttributesForElements(in rect: CGRect) erhalten wir einfach die Indexpfade aller Zellen in der rectund rufen dann für jeden Indexpfad die erste Funktion 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 Funktion copy() erstellt einfach eine tiefe Kopie aller Layoutattribute im Array. Sie können die Implementierung in Quellcode nachlesen.)

Jetzt müssen wir nur noch die Funktion layoutAttributesForItem(at indexPath: IndexPath) richtig implementieren. Die Superklasse UICollectionViewFlowLayoutplatziert bereits die richtige Anzahl von Zellen in jeder Zeile, sodass wir sie nur innerhalb der jeweiligen Zeile nach links verschieben müssen. Die Schwierigkeit liegt in der Berechnung des Raums, den wir benötigen, um jede Zelle nach links zu verschieben.

Da ein fester Abstand zwischen den Zellen gewünscht wird, wird im Kern nur davon ausgegangen, dass die vorherige Zelle (die Zelle links von der aktuell ausgelegten Zelle) bereits richtig positioniert ist. Dann müssen wir nur den Zellenabstand zum Wert maxXdes vorherigen Zellenrahmens hinzufügen, und das ist der Wert Origin.x für den aktuellen Zellenrahmen.

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 zu einer extremen Verzögerung.) Wir benötigen also einen Rekursionsanker. Der Ansatz, mit dem ich diesen Rekursionsanker finde, ist der folgende:

Um herauszufinden, ob sich die Zelle mit dem 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 UICollectionViewFlowLayoutalle Zellen vertikal zentriert, schneidet sich jede Zelle in derselben Zeile muss mit diesem Rechteck.

Daher überprüfe ich einfach, ob sich die Zelle mit dem Index i-1 mit diesem Linienrechteck überschneidet, das aus der Zelle mit dem Index i erstellt wurde.

  • Wenn es sich überschneidet, 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.
    → Verschieben Sie die Zelle an den linken Rand der Sammlungsansicht (ohne ihre vertikale Position zu ändern).

Ich werde die tatsächliche Implementierung der Funktion layoutAttributesForItem(at indexPath: IndexPath) hier nicht veröffentlichen, da ich der Meinung bin, dass der wichtigste Teil darin besteht, die Idee zu verstehen, und Sie können meine Implementierung immer im Quellcode überprüfen . (Es ist etwas komplizierter als hier erklärt, da ich auch die Ausrichtung von .right und verschiedene vertikale Ausrichtungsoptionen zulasse. Es folgt jedoch der gleichen Idee.)


Wow, ich denke das ist die längste Antwort, die ich jemals auf Stackoverflow geschrieben habe. Ich hoffe das hilft. ????

36
Mischa

Mit Swift 4.1 und iOS 11 können Sie je nach Bedarf eine der folgenden 2 vollständigen Implementierungen in auswählen um Ihr Problem zu beheben.


# 1. Automatische Größenanpassung von UICollectionViewCells nach links ausrichten

Die folgende Implementierung zeigt, wie Sie UICollectionViewLayouts layoutAttributesForElements(in:) , UICollectionViewFlowLayouts estimatedItemSize verwenden und UILabel 's preferredMaxLayoutWidth , um Zellen mit automatischer Größe in einem UICollectionView nach links auszurichten:

CollectionViewController.Swift

import UIKit

class CollectionViewController: UICollectionViewController {

    let array = ["1", "1 2", "1 2 3 4 5 6 7 8", "1 2 3 4 5 6 7 8 9 10 11", "1 2 3", "1 2 3 4", "1 2 3 4 5 6", "1 2 3 4 5 6 7 8 9 10", "1 2 3 4", "1 2 3 4 5 6 7", "1 2 3 4 5 6 7 8 9", "1", "1 2 3 4 5", "1", "1 2 3 4 5 6"]

    let columnLayout = FlowLayout(
        minimumInteritemSpacing: 10,
        minimumLineSpacing: 10,
        sectionInset: UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
    )

    override func viewDidLoad() {
        super.viewDidLoad()

        collectionView?.collectionViewLayout = columnLayout
        collectionView?.contentInsetAdjustmentBehavior = .always
        collectionView?.register(CollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
    }

    override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return array.count
    }

    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! CollectionViewCell
        cell.label.text = array[indexPath.row]
        return cell
    }

    override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        collectionView?.collectionViewLayout.invalidateLayout()
        super.viewWillTransition(to: size, with: coordinator)
    }

}

FlowLayout.Swift

import UIKit

class FlowLayout: UICollectionViewFlowLayout {

    required init(minimumInteritemSpacing: CGFloat = 0, minimumLineSpacing: CGFloat = 0, sectionInset: UIEdgeInsets = .zero) {
        super.init()

        estimatedItemSize = UICollectionViewFlowLayoutAutomaticSize
        self.minimumInteritemSpacing = minimumInteritemSpacing
        self.minimumLineSpacing = minimumLineSpacing
        self.sectionInset = sectionInset
        sectionInsetReference = .fromSafeArea
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        let layoutAttributes = super.layoutAttributesForElements(in: rect)!.map { $0.copy() as! UICollectionViewLayoutAttributes }
        guard scrollDirection == .vertical else { return layoutAttributes }

        // Filter attributes to compute only cell attributes
        let cellAttributes = layoutAttributes.filter({ $0.representedElementCategory == .cell })

        // Group cell attributes by row (cells with same vertical center) and loop on those groups
        for (_, attributes) in Dictionary(grouping: cellAttributes, by: { ($0.center.y / 10).rounded(.up) * 10 }) {
            // Set the initial left inset
            var leftInset = sectionInset.left

            // Loop on cells to adjust each cell's Origin and prepare leftInset for the next cell
            for attribute in attributes {
                attribute.frame.Origin.x = leftInset
                leftInset = attribute.frame.maxX + minimumInteritemSpacing
            }
        }

        return layoutAttributes
    }

}

CollectionViewCell.Swift

import UIKit

class CollectionViewCell: UICollectionViewCell {

    let label = UILabel()

    override init(frame: CGRect) {
        super.init(frame: frame)

        contentView.backgroundColor = .orange
        label.preferredMaxLayoutWidth = 120
        label.numberOfLines = 0

        contentView.addSubview(label)
        label.translatesAutoresizingMaskIntoConstraints = false
        contentView.layoutMarginsGuide.topAnchor.constraint(equalTo: label.topAnchor).isActive = true
        contentView.layoutMarginsGuide.leadingAnchor.constraint(equalTo: label.leadingAnchor).isActive = true
        contentView.layoutMarginsGuide.trailingAnchor.constraint(equalTo: label.trailingAnchor).isActive = true
        contentView.layoutMarginsGuide.bottomAnchor.constraint(equalTo: label.bottomAnchor).isActive = true
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

}

Erwartetes Ergebnis:

enter image description here


# 2. Richten Sie UICollectionViewCells links an einer festen Größe aus

Die folgende Implementierung zeigt, wie Sie UICollectionViewLayouts layoutAttributesForElements(in:) und UICollectionViewFlowLayouts itemSize verwenden Um Zellen mit vordefinierter Größe linksbündig in einem UICollectionView auszurichten:

CollectionViewController.Swift

import UIKit

class CollectionViewController: UICollectionViewController {

    let columnLayout = FlowLayout(
        itemSize: CGSize(width: 140, height: 140),
        minimumInteritemSpacing: 10,
        minimumLineSpacing: 10,
        sectionInset: UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
    )

    override func viewDidLoad() {
        super.viewDidLoad()

        collectionView?.collectionViewLayout = columnLayout
        collectionView?.contentInsetAdjustmentBehavior = .always
        collectionView?.register(CollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
    }

    override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 7
    }

    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! CollectionViewCell
        return cell
    }

    override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        collectionView?.collectionViewLayout.invalidateLayout()
        super.viewWillTransition(to: size, with: coordinator)
    }

}

FlowLayout.Swift

import UIKit

class FlowLayout: UICollectionViewFlowLayout {

    required init(itemSize: CGSize, minimumInteritemSpacing: CGFloat = 0, minimumLineSpacing: CGFloat = 0, sectionInset: UIEdgeInsets = .zero) {
        super.init()

        self.itemSize = itemSize
        self.minimumInteritemSpacing = minimumInteritemSpacing
        self.minimumLineSpacing = minimumLineSpacing
        self.sectionInset = sectionInset
        sectionInsetReference = .fromSafeArea
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        let layoutAttributes = super.layoutAttributesForElements(in: rect)!.map { $0.copy() as! UICollectionViewLayoutAttributes }
        guard scrollDirection == .vertical else { return layoutAttributes }

        // Filter attributes to compute only cell attributes
        let cellAttributes = layoutAttributes.filter({ $0.representedElementCategory == .cell })

        // Group cell attributes by row (cells with same vertical center) and loop on those groups
        for (_, attributes) in Dictionary(grouping: cellAttributes, by: { ($0.center.y / 10).rounded(.up) * 10 }) {
            // Set the initial left inset
            var leftInset = sectionInset.left

            // Loop on cells to adjust each cell's Origin and prepare leftInset for the next cell
            for attribute in attributes {
                attribute.frame.Origin.x = leftInset
                leftInset = attribute.frame.maxX + minimumInteritemSpacing
            }
        }

        return layoutAttributes
    }

}

CollectionViewCell.Swift

import UIKit

class CollectionViewCell: UICollectionViewCell {

    override init(frame: CGRect) {
        super.init(frame: frame)

        contentView.backgroundColor = .cyan
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

}

Erwartetes Ergebnis:

enter image description here

21
Imanou Petit

Die Frage ist schon eine Weile auf, aber es gibt keine Antwort und es ist eine gute Frage. Die Antwort besteht darin, eine Methode in der UICollectionViewFlowLayout-Unterklasse zu überschreiben: 

@implementation MYFlowLayoutSubclass

//Note, the layout's minimumInteritemSpacing (default 10.0) should not be less than this. 
#define ITEM_SPACING 10.0f

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {

    NSArray *attributesForElementsInRect = [super layoutAttributesForElementsInRect:rect];
    NSMutableArray *newAttributesForElementsInRect = [[NSMutableArray alloc] initWithCapacity:attributesForElementsInRect.count];

    CGFloat leftMargin = self.sectionInset.left; //initalized to silence compiler, and actaully safer, but not planning to use.

    //this loop assumes attributes are in IndexPath order
    for (UICollectionViewLayoutAttributes *attributes in attributesForElementsInRect) {
        if (attributes.frame.Origin.x == self.sectionInset.left) {
            leftMargin = self.sectionInset.left; //will add outside loop
        } else {
            CGRect newLeftAlignedFrame = attributes.frame;
            newLeftAlignedFrame.Origin.x = leftMargin;
            attributes.frame = newLeftAlignedFrame;
        }

        leftMargin += attributes.frame.size.width + ITEM_SPACING;
        [newAttributesForElementsInRect addObject:attributes];
    }   

    return newAttributesForElementsInRect;
}

@end

Wie von Apple empfohlen, erhalten Sie die Layout-Attribute von super und iterieren über sie. Wenn es der erste in der Zeile ist (definiert durch Origin.x am linken Rand), lassen Sie ihn in Ruhe und setzen das x auf Null zurück. Dann addieren Sie für die erste Zelle und jede Zelle die Breite dieser Zelle plus etwas Rand. Dies wird an das nächste Element in der Schleife übergeben. Wenn es nicht das erste Element ist, setzen Sie Origin.x auf den laufenden berechneten Rand und fügen dem Array neue Elemente hinzu. 

20
Mike Sand

Ich hatte das gleiche Problem, Gib das Cocoapod UICollectionViewLeftAlignedLayout einen Versuch. Fügen Sie es einfach in Ihr Projekt ein und initialisieren Sie es wie folgt:

UICollectionViewLeftAlignedLayout *layout = [[UICollectionViewLeftAlignedLayout alloc] init];
UICollectionView *leftAlignedCollectionView = [[UICollectionView alloc] initWithFrame:frame collectionViewLayout:layout];
10
mklb

Aufbauend auf der Antwort von Michael Sand habe ich eine untergeordnete UICollectionViewFlowLayout-Bibliothek erstellt, die links, rechts oder vollständig (im Wesentlichen die Standardeinstellung) horizontal ausgerichtet ist. Hier können Sie auch den absoluten Abstand zwischen den einzelnen Zellen festlegen. Ich habe vor, auch die horizontale Ausrichtung in der Mitte und die vertikale Ausrichtung hinzuzufügen.

https://github.com/eroth/ERJustifiedFlowLayout

5
Evan R

In Swift. Laut Michaels Antwort

override func layoutAttributesForElementsInRect(rect: CGRect) ->     [UICollectionViewLayoutAttributes]? {
    guard let oldAttributes = super.layoutAttributesForElementsInRect(rect) else {
        return super.layoutAttributesForElementsInRect(rect)
    }
    let spacing = CGFloat(50) // REPLACE WITH WHAT SPACING YOU NEED
    var newAttributes = [UICollectionViewLayoutAttributes]()
    var leftMargin = self.sectionInset.left
    for attributes in oldAttributes {
        if (attributes.frame.Origin.x == self.sectionInset.left) {
            leftMargin = self.sectionInset.left
        } else {
            var newLeftAlignedFrame = attributes.frame
            newLeftAlignedFrame.Origin.x = leftMargin
            attributes.frame = newLeftAlignedFrame
        }

        leftMargin += attributes.frame.width + spacing
        newAttributes.append(attributes)
    }
    return newAttributes
}
5
GregP

Hier ist die ursprüngliche Antwort in Swift. Meistens funktioniert es immer noch großartig.

class LeftAlignedFlowLayout: UICollectionViewFlowLayout {

    private override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        let attributes = super.layoutAttributesForElementsInRect(rect)

        var leftMargin = sectionInset.left

        attributes?.forEach { layoutAttribute in
            if layoutAttribute.frame.Origin.x == sectionInset.left {
                leftMargin = sectionInset.left
            }
            else {
                layoutAttribute.frame.Origin.x = leftMargin
            }

            leftMargin += layoutAttribute.frame.width + minimumInteritemSpacing
        }

        return attributes
    }
}

Ausnahme: Autosizing-Zellen

Es gibt leider eine große Ausnahme. Wenn Sie UICollectionViewFlowLayouts estimatedItemSize verwenden. Intern verändert UICollectionViewFlowLayout die Dinge ein wenig. Ich habe es nicht ganz aufgespürt, aber es ist klar, dass es nach layoutAttributesForElementsInRect andere Methoden aufgerufen hat, während es Zellen selbst skaliert. Nach meinen Versuchen und Irrtümern habe ich festgestellt, dass es während des Autosizing häufiger für jede Zelle einzeln layoutAttributesForItemAtIndexPath heißt. Diese aktualisierte LeftAlignedFlowLayout funktioniert gut mit estimatedItemSize. Es funktioniert auch mit Zellen mit statischer Größe, aber die zusätzlichen Layoutaufrufe führen dazu, dass ich die ursprüngliche Antwort immer dann verwende, wenn ich keine Autosizing-Zellen benötige.

class LeftAlignedFlowLayout: UICollectionViewFlowLayout {

    private override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? {
        let layoutAttribute = super.layoutAttributesForItemAtIndexPath(indexPath)?.copy() as? UICollectionViewLayoutAttributes

        // First in a row.
        if layoutAttribute?.frame.Origin.x == sectionInset.left {
            return layoutAttribute
        }

        // We need to align it to the previous item.
        let previousIndexPath = NSIndexPath(forItem: indexPath.item - 1, inSection: indexPath.section)
        guard let previousLayoutAttribute = self.layoutAttributesForItemAtIndexPath(previousIndexPath) else {
            return layoutAttribute
        }

        layoutAttribute?.frame.Origin.x = previousLayoutAttribute.frame.maxX + self.minimumInteritemSpacing

        return layoutAttribute
    }
}
4
Ryan Poolos

Basierend auf allen Antworten verändere ich mich ein bisschen und es funktioniert gut für mich

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

    var leftMargin = sectionInset.left
    var maxY: CGFloat = -1.0


    attributes?.forEach { layoutAttribute in
        if layoutAttribute.frame.Origin.y >= maxY
                   || layoutAttribute.frame.Origin.x == sectionInset.left {
            leftMargin = sectionInset.left
        }

        if layoutAttribute.frame.Origin.x == sectionInset.left {
            leftMargin = sectionInset.left
        }
        else {
            layoutAttribute.frame.Origin.x = leftMargin
        }

        leftMargin += layoutAttribute.frame.width
        maxY = max(layoutAttribute.frame.maxY, maxY)
    }

    return attributes
}
3
kotvaska

Danke für die Antwort von Michael Sand . Ich habe es zu einer Lösung mit mehreren Zeilen (derselben Ausrichtung, oberer y jeder Zeile) geändert, die links ausgerichtet ist, sogar Abstand zu jedem Element.

static CGFloat const ITEM_SPACING = 10.0f;

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
    CGRect contentRect = {CGPointZero, self.collectionViewContentSize};

    NSArray *attributesForElementsInRect = [super layoutAttributesForElementsInRect:contentRect];
    NSMutableArray *newAttributesForElementsInRect = [[NSMutableArray alloc] initWithCapacity:attributesForElementsInRect.count];

    CGFloat leftMargin = self.sectionInset.left; //initalized to silence compiler, and actaully safer, but not planning to use.
    NSMutableDictionary *leftMarginDictionary = [[NSMutableDictionary alloc] init];

    for (UICollectionViewLayoutAttributes *attributes in attributesForElementsInRect) {
        UICollectionViewLayoutAttributes *attr = attributes.copy;

        CGFloat lastLeftMargin = [[leftMarginDictionary valueForKey:[[NSNumber numberWithFloat:attributes.frame.Origin.y] stringValue]] floatValue];
        if (lastLeftMargin == 0) lastLeftMargin = leftMargin;

        CGRect newLeftAlignedFrame = attr.frame;
        newLeftAlignedFrame.Origin.x = lastLeftMargin;
        attr.frame = newLeftAlignedFrame;

        lastLeftMargin += attr.frame.size.width + ITEM_SPACING;
        [leftMarginDictionary setObject:@(lastLeftMargin) forKey:[[NSNumber numberWithFloat:attributes.frame.Origin.y] stringValue]];
        [newAttributesForElementsInRect addObject:attr];
    }

    return newAttributesForElementsInRect;
}
1
Shu Zhang

Angel García Olloquis Antwort auf minimumInteritemSpacing von Delegated's collectionView(_:layout:minimumInteritemSpacingForSectionAt:) bearbeitet, falls er sie implementiert.

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

    var leftMargin = sectionInset.left
    var maxY: CGFloat = -1.0
    attributes?.forEach { layoutAttribute in
        if layoutAttribute.frame.Origin.y >= maxY {
            leftMargin = sectionInset.left
        }

        layoutAttribute.frame.Origin.x = leftMargin

        let delegate = collectionView?.delegate as? UICollectionViewDelegateFlowLayout
        let spacing = delegate?.collectionView?(collectionView!, layout: self, minimumInteritemSpacingForSectionAt: 0) ?? minimumInteritemSpacing

        leftMargin += layoutAttribute.frame.width + spacing
        maxY = max(layoutAttribute.frame.maxY , maxY)
    }

    return attributes
}
0
Radek

Das Problem mit UICollectionView besteht darin, dass versucht wird, die Zellen automatisch in den verfügbaren Bereich zu passen. Ich habe dies getan, indem ich zuerst die Anzahl der Zeilen und Spalten und dann die Zellengröße für diese Zeile und Spalte definiert habe

1) So definieren Sie Abschnitte (Zeilen) meiner UICollectionView:

(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView

2) Anzahl der Elemente in einem Abschnitt definieren. Sie können für jeden Abschnitt eine unterschiedliche Anzahl von Elementen definieren. Sie können die Abschnittsnummer über den Parameter 'section' erhalten.

(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section

3) Definieren der Zellengröße für jeden Abschnitt und jede Zeile separat. Abschnittsnummer und Zeilennummer erhalten Sie mit dem Parameter 'indexPath', d. H.[indexPath section]für Abschnittsnummer und[indexPath row]für Zeilennummer.

(CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath

4) Dann können Sie Ihre Zellen in Zeilen und Abschnitten anzeigen, indem Sie:

(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath

HINWEIS: In UICollectionView

Section == Row
IndexPath.Row == Column
0
Anas iqbal

Die Antwort von Mike Sand ist gut, aber ich hatte einige Probleme mit diesem Code (wie lange Zelle herausgeschnitten). Und neuer Code:

#define ITEM_SPACE 7.0f

@implementation LeftAlignedCollectionViewFlowLayout
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
    NSArray* attributesToReturn = [super layoutAttributesForElementsInRect:rect];
    for (UICollectionViewLayoutAttributes* attributes in attributesToReturn) {
        if (nil == attributes.representedElementKind) {
            NSIndexPath* indexPath = attributes.indexPath;
            attributes.frame = [self layoutAttributesForItemAtIndexPath:indexPath].frame;
        }
    }
    return attributesToReturn;
}

- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
    UICollectionViewLayoutAttributes* currentItemAttributes =
    [super layoutAttributesForItemAtIndexPath:indexPath];

    UIEdgeInsets sectionInset = [(UICollectionViewFlowLayout *)self.collectionView.collectionViewLayout sectionInset];

    if (indexPath.item == 0) { // first item of section
        CGRect frame = currentItemAttributes.frame;
        frame.Origin.x = sectionInset.left; // first item of the section should always be left aligned
        currentItemAttributes.frame = frame;

        return currentItemAttributes;
    }

    NSIndexPath* previousIndexPath = [NSIndexPath indexPathForItem:indexPath.item-1 inSection:indexPath.section];
    CGRect previousFrame = [self layoutAttributesForItemAtIndexPath:previousIndexPath].frame;
    CGFloat previousFrameRightPoint = previousFrame.Origin.x + previousFrame.size.width + ITEM_SPACE;

    CGRect currentFrame = currentItemAttributes.frame;
    CGRect strecthedCurrentFrame = CGRectMake(0,
                                              currentFrame.Origin.y,
                                              self.collectionView.frame.size.width,
                                              currentFrame.size.height);

    if (!CGRectIntersectsRect(previousFrame, strecthedCurrentFrame)) { // if current item is the first item on the line
        // the approach here is to take the current frame, left align it to the Edge of the view
        // then stretch it the width of the collection view, if it intersects with the previous frame then that means it
        // is on the same line, otherwise it is on it's own new line
        CGRect frame = currentItemAttributes.frame;
        frame.Origin.x = sectionInset.left; // first item on the line should always be left aligned
        currentItemAttributes.frame = frame;
        return currentItemAttributes;
    }

    CGRect frame = currentItemAttributes.frame;
    frame.Origin.x = previousFrameRightPoint;
    currentItemAttributes.frame = frame;
    return currentItemAttributes;
}
0
Lal Krishna

Der obige Code funktioniert für mich. Ich möchte den entsprechenden Swift 3.0-Code mitteilen.

class SFFlowLayout: UICollectionViewFlowLayout {

    let itemSpacing: CGFloat = 3.0

    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {

        let attriuteElementsInRect = super.layoutAttributesForElements(in: rect)
        var newAttributeForElement: Array<UICollectionViewLayoutAttributes> = []
        var leftMargin = self.sectionInset.left
        for tempAttribute in attriuteElementsInRect! {
            let attribute = tempAttribute 
            if attribute.frame.Origin.x == self.sectionInset.left {
                leftMargin = self.sectionInset.left
            }
            else {
                var newLeftAlignedFrame = attribute.frame
                newLeftAlignedFrame.Origin.x = leftMargin
                attribute.frame = newLeftAlignedFrame
            }
            leftMargin += attribute.frame.size.width + itemSpacing
            newAttributeForElement.append(attribute)
        }
        return newAttributeForElement
    }
}
0
Naresh

Basierend auf den Antworten hier, wurden jedoch Abstürze und Ausrichtungsprobleme behoben, wenn Ihre Sammlungsansicht auch Kopf- oder Fußzeilen enthält. Nur linke Zellen ausrichten:

class LeftAlignedCollectionViewFlowLayout: UICollectionViewFlowLayout {

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

        var leftMargin = sectionInset.left
        var prevMaxY: CGFloat = -1.0
        attributes?.forEach { layoutAttribute in

            guard layoutAttribute.representedElementCategory == .cell else {
                return
            }

            if layoutAttribute.frame.Origin.y >= prevMaxY {
                leftMargin = sectionInset.left
            }

            layoutAttribute.frame.Origin.x = leftMargin

            leftMargin += layoutAttribute.frame.width + minimumInteritemSpacing
            prevMaxY = layoutAttribute.frame.maxY
        }

        return attributes
    }
}
0
Alex Shubin