web-dev-qa-db-fra.com

Aligner à gauche les cellules dans UICollectionView

J'utilise UICollectionView dans mon projet, où il y a plusieurs cellules de différentes largeurs sur une ligne. Selon: https://developer.Apple.com/library/content/documentation/WindowsViews/Conceptual/CollectionViewPGforIOS/UsingtheFlowLayout/UsingtheFlowLayout.html

il répartit les cellules sur la ligne avec un remplissage égal. Cela se passe comme prévu, sauf que je veux justifier à gauche, et coder en dur une largeur de remplissage.

Je suppose que je dois sous-classer UICollectionViewFlowLayout. Cependant, après la lecture de certains tutoriels en ligne, etc., il me semble que je ne comprends pas comment cela fonctionne.

62
Damien

Les autres solutions présentées ici ne fonctionnent pas correctement lorsque la ligne est composée d’un seul élément ou est trop compliquée.

Sur la base de l'exemple donné par Ryan, j'ai modifié le code pour détecter une nouvelle ligne en inspectant la position Y du nouvel élément. Très simple et rapide en performance.

Rapide: 

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

Si vous souhaitez que les vues supplémentaires conservent leur taille, ajoutez ce qui suit en haut de la fermeture dans l'appel forEach:

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

Objectif 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
Angel G. Olloqui

Les réponses à cette question contiennent de nombreuses bonnes idées. Cependant, la plupart d’entre eux présentent des inconvénients:

  • Les solutions qui ne vérifient pas la valeur y de la cellule ne fonctionnent que pour les dispositions d'une seule ligne . Ils échouent pour les présentations de collection avec plusieurs lignes.
  • Les solutions que font vérifient la valeur y comme réponse d'Angel García Olloqui ne fonctionne que si toutes les cellules ont la même hauteur . Ils échouent pour les cellules de hauteur variable.
  • La plupart des solutions ne font que remplacer la fonction layoutAttributesForElements(in rect: CGRect). Ils ne remplacent pas layoutAttributesForItem(at indexPath: IndexPath). Il s'agit d'un problème car la vue de collection appelle périodiquement cette dernière fonction pour extraire les attributs de présentation d'un chemin d'index particulier. Si vous ne renvoyez pas les attributs appropriés à partir de cette fonction, vous rencontrerez probablement toutes sortes de bugs visuels, par exemple. lors de l'insertion et de la suppression d'animations de cellules ou lors de l'utilisation de cellules à taille automatique en définissant la structure de la vue de la collection dans estimatedItemSizename__. Les documents Apple indiquent:

    Chaque objet de présentation personnalisé doit implémenter la méthode layoutAttributesForItemAtIndexPath:.

  • De nombreuses solutions supposent également le paramètre rectqui est transmis à la fonction layoutAttributesForElements(in rect: CGRect). Par exemple, beaucoup sont basés sur l'hypothèse que rectcommence toujours au début d'une nouvelle ligne, ce qui n'est pas nécessairement le cas.

Donc en d'autres termes:

La plupart des solutions suggérées sur cette page fonctionnent pour certaines applications spécifiques, mais ne fonctionnent pas comme prévu dans toutes les situations.


AlignedCollectionViewFlowLayout

Afin de résoudre ces problèmes, j'ai créé une sous-classe UICollectionViewFlowLayoutqui suit une idée similaire à celle suggérée par matt et Chris Wagner dans leurs réponses à une question similaire. Il peut soit aligner les cellules

⬅︎  à gauche :

Left-aligned layout

ou ➡︎  right :

Right-aligned layout

et offre en plus des options pour verticalement aligner les cellules dans leurs rangées respectives (si leur hauteur varie).

Vous pouvez simplement le télécharger ici:

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

L'utilisation est simple et expliquée dans le fichier README. En gros, vous créez une instance de AlignedCollectionViewFlowLayoutname__, spécifiez l'alignement souhaité et affectez-la à la propriété collectionViewLayoutde votre vue de collection:

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

 yourCollectionView.collectionViewLayout = alignedFlowLayout

(Il est également disponible sur Cocoapods .)


Comment ça marche (pour les cellules alignées à gauche):

Le concept ici est de s'appuyer uniquement sur la fonction layoutAttributesForItem(at indexPath: IndexPath). Dans la layoutAttributesForElements(in rect: CGRect), nous obtenons simplement les chemins d'index de toutes les cellules dans la rectname__, puis appelons la première fonction de chaque chemin d'index pour récupérer les trames correctes:

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
}

(La fonction copy() crée simplement une copie détaillée de tous les attributs de présentation du tableau. Vous pouvez consulter le code source pour connaître son implémentation.)

Il ne reste donc maintenant qu’à implémenter correctement la fonction layoutAttributesForItem(at indexPath: IndexPath). La super classe UICollectionViewFlowLayoutplace déjà le nombre correct de cellules dans chaque ligne, il ne reste donc plus qu'à les déplacer à gauche dans leur ligne respective. La difficulté réside dans le calcul de la quantité d’espace nécessaire pour décaler chaque cellule de gauche.

Comme nous voulons avoir un espacement fixe entre les cellules, l’idée principale est de supposer que la cellule précédente (la cellule de gauche de la cellule actuellement agencée) est déjà correctement positionnée. Ensuite, il suffit d'ajouter l'espacement des cellules à la valeur maxXdu cadre de la cellule précédente. Il s'agit de la valeur Origin.x du cadre de la cellule actuelle.

Maintenant, nous avons seulement besoin de savoir quand nous avons atteint le début d'une ligne pour ne pas aligner une cellule à côté d'une cellule dans la ligne précédente. (Cela entraînerait non seulement une mise en page incorrecte, mais aussi une latence extrême.) Nous avons donc besoin d'une ancre de récursivité. L'approche que j'utilise pour trouver cette ancre de récursion est la suivante:

Pour savoir si la cellule à l'index i est dans la même ligne que la cellule à l'index i-1 ...

 +---------+----------------------------------------------------------------+---------+
 |         |                                                                |         |
 |         |     +------------+                                             |         |
 |         |     |            |                                             |         |
 | section |- - -|- - - - - - |- - - - +---------------------+ - - - - - - -| section |
 |  inset  |     |intersection|        |                     |   line rect  |  inset  |
 |         |- - -|- - - - - - |- - - - +---------------------+ - - - - - - -|         |
 | (left)  |     |            |             current item                    | (right) |
 |         |     +------------+                                             |         |
 |         |     previous item                                              |         |
 +---------+----------------------------------------------------------------+---------+

... Je "dessine" un rectangle autour de la cellule actuelle et l'étire sur la largeur de la vue de la collection entière. Comme UICollectionViewFlowLayoutcentre toutes les cellules verticalement, chaque cellule de la même ligne doit se croiser avec ce rectangle.

Ainsi, je vérifie simplement si la cellule avec index i-1 intersecte avec ce rectangle linéaire créé à partir de la cellule avec index i .

  • Si elle se croise, la cellule d'indice i n'est pas la cellule la plus à gauche de la ligne.
    → Obtenir le cadre de la cellule précédente (avec l'index i − 1 ) et déplacer la cellule actuelle à côté.

  • Si elle ne se croise pas, la cellule d'indice i est la cellule la plus à gauche de la ligne.
    → Déplacez la cellule vers le bord gauche de la vue de collection (sans changer sa position verticale).

Je ne posterai pas ici l'implémentation réelle de la fonction layoutAttributesForItem(at indexPath: IndexPath) car je pense que le plus important est de comprendre l'idée et vous pouvez toujours vérifier mon implémentation dans le code source . (C’est un peu plus compliqué que ce qui est expliqué ici, car j’autorise également l’alignement .right et diverses options d’alignement vertical. Toutefois, il suit la même idée.)


Wow, je suppose que c'est la réponse la plus longue que j'ai jamais écrite sur Stackoverflow. J'espère que ça aide. ????

36
Mischa

La question a été posée il y a longtemps, mais il n'y a pas de réponse et c'est une bonne question. La solution consiste à remplacer une méthode de la sous-classe UICollectionViewFlowLayout: 

@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

Conformément aux recommandations de Apple, vous obtenez les attributs de présentation de super et les parcourez par-dessus. Si c'est le premier de la ligne (défini par son Origin.x situé dans la marge de gauche), vous le laissez seul et réinitialisez le x à zéro. Ensuite, pour la première cellule et chaque cellule, vous ajoutez la largeur de cette cellule plus une marge. Ceci est passé à l'élément suivant de la boucle. S'il ne s'agit pas du premier élément, définissez Origin.x sur la marge calculée en cours d'exécution et ajoutez de nouveaux éléments au tableau. 

20
Mike Sand

J'ai eu le même problème, Donnez le Cocoapod UICollectionViewLeftAlignedLayout a essayer. Il suffit de l'inclure dans votre projet et de l'initialiser comme ceci:

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

En me basant sur La réponse de Michael Sand , j'ai créé une bibliothèque UICollectionViewFlowLayout sous-classée pour effectuer une justification horizontale gauche, droite ou complète (par défaut); elle vous permet également de définir la distance absolue entre chaque cellule. Je prévois d'y ajouter également une justification centrale horizontale et une justification verticale.

https://github.com/eroth/ERJustifiedFlowLayout

5
Evan R

En rapide. Selon Michaels répondre

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

Voici la réponse originale dans Swift. Cela fonctionne toujours très bien la plupart du temps.

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

Exception: Autosizing Cells

Il y a une grande exception malheureusement. Si vous utilisez UICollectionViewFlowLayout 's estimatedItemSize. En interne, UICollectionViewFlowLayout change un peu les choses. Je ne l'ai pas encore entièrement repéré, mais il est clair que d'autres méthodes sont appelées après layoutAttributesForElementsInRect tout en dimensionnant les cellules. D'après mes essais, j'ai trouvé qu'il semble appeler layoutAttributesForItemAtIndexPath pour chaque cellule individuellement pendant l'autosizing plus souvent. Cette mise à jour LeftAlignedFlowLayout fonctionne très bien avec estimatedItemSize. Cela fonctionne également avec les cellules de taille statique, mais les appels de mise en page supplémentaires me conduisent à utiliser la réponse originale chaque fois que je n'ai pas besoin de cellules à taille automatique.

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

Sur la base de toutes les réponses, je change un peu et cela fonctionne bien pour moi

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

Merci pour le réponse de Michael Sand . Je l'ai modifié pour une solution de plusieurs lignes (le même alignement en haut de chaque ligne) qui correspond à l'alignement de gauche, même espacement pour chaque élément.

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

Édité la réponse d'Angel García Olloqui à respecter minimumInteritemSpacing de la fonction collectionView(_:layout:minimumInteritemSpacingForSectionAt:) du délégué, si celle-ci est implémentée.

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

Le problème avec UICollectionView est qu’il essaie d’ajuster automatiquement les cellules dans la zone disponible ..___ Je l’ai fait en définissant d’abord le nombre de lignes et de colonnes, puis la taille de cellule pour cette ligne et cette colonne

1) Pour définir les sections (lignes) de mon UICollectionView:

(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView

2) Définir le nombre d'éléments dans une section. Vous pouvez définir un nombre différent d'éléments pour chaque section. vous pouvez obtenir le numéro de section en utilisant le paramètre 'section'.

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

3) Définir la taille de cellule pour chaque section et chaque rangée séparément. Vous pouvez obtenir le numéro de section et le numéro de ligne en utilisant le paramètre 'indexPath', par exemple,[indexPath section]pour le numéro de section et[indexPath row]pour le numéro de ligne.

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

4) Ensuite, vous pouvez afficher vos cellules en lignes et en sections en utilisant:

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

REMARQUE: Dans UICollectionView

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

La réponse de Mike Sand est bonne mais j'avais rencontré quelques problèmes avec ce code (comme une longue cellule coupée). Et nouveau 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

Le code ci-dessus fonctionne pour moi. Je voudrais partager le code respectif Swift 3.0.

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

Sur la base des réponses fournies ici, mais des plantages corrigés et des problèmes d’alignement lorsque votre vue de collection contient également des en-têtes ou des pieds de page. Aligner les cellules restantes:

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