Comment centrer aligner les cellules d'un UICollectionView?

99

J'utilise actuellement UICollectionViewpour la grille d'interface utilisateur, et cela fonctionne très bien. Cependant, j'aimerais pouvoir activer le défilement horizontal. La grille prend en charge 8 éléments par page et lorsque le nombre total d'éléments est, par exemple, 4, voici comment les éléments doivent être organisés avec le sens de défilement horizontal activé:

0 0 x x
0 0 x x

Ici 0 -> Élément de collection et x -> Cellules vides

Existe-t-il un moyen de les aligner au centre comme:

x 0 0 x
x 0 0 x

Pour que le contenu soit plus propre?

L'arrangement ci-dessous pourrait également être une solution que j'attends.

0 0 0 0
x x x x
RVN
la source

Réponses:

80

Je pense que vous pouvez obtenir le look à une seule ligne en implémentant quelque chose comme ceci:

- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {
    return UIEdgeInsetsMake(0, 100, 0, 0);
}

Vous devrez jouer avec ce nombre pour comprendre comment forcer le contenu sur une seule ligne. Le premier 0 est l'argument du bord supérieur, vous pouvez également l'ajuster si vous souhaitez centrer le contenu verticalement sur l'écran.

rdelmar
la source
1
C'est une bonne solution merci, mais cela nécessite des calculs supplémentaires de mon côté.
RVN
Comment calculez-vous ces valeurs?
jjxtra
7
POUR calculer dynamiquement les encarts: CGFloat leftInset = (collectionView.frame.size.width-40 * [collectionView numberOfItemsInSection: section]) / 2; 40 = taille de la cellule
Abduliam Rehmanius
1
@AbduliamRehmanius Mais cela suppose que les cellules ne sont pas espacées horizontalement. Ils le sont généralement. Et il ne semble pas simple de déterminer les espaces réels car minimumInteritemSpacingn'indique que leur minimum.
Drux
5
Cela devrait en fait être return UIEdgeInsetsMake(0, 100, 0, 100);. S'il y a plus d'espace, la vue de la collection élargira l'espacement intermédiaire, vous devez donc vous assurer que LeftInset + CellWidth * Ncells + CellSpacing * (Ncells-1) + RightInset = CollectionViewWidth
vish
83

Pour ceux qui recherchent une solution aux cellules de vue de collection alignées au centre et à largeur dynamique , comme je l'étais, j'ai fini par modifier la réponse d'Angel pour une version alignée à gauche pour créer une sous-classe alignée au centre UICollectionViewFlowLayout.

CenterAlignedCollectionViewFlowLayout

// NOTE: Doesn't work for horizontal layout!
class CenterAlignedCollectionViewFlowLayout: UICollectionViewFlowLayout {
    
    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        guard let superAttributes = super.layoutAttributesForElements(in: rect) else { return nil }
        // Copy each item to prevent "UICollectionViewFlowLayout has cached frame mismatch" warning
        guard let attributes = NSArray(array: superAttributes, copyItems: true) as? [UICollectionViewLayoutAttributes] else { return nil }
        
        // Constants
        let leftPadding: CGFloat = 8
        let interItemSpacing = minimumInteritemSpacing
        
        // Tracking values
        var leftMargin: CGFloat = leftPadding // Modified to determine origin.x for each item
        var maxY: CGFloat = -1.0 // Modified to determine origin.y for each item
        var rowSizes: [[CGFloat]] = [] // Tracks the starting and ending x-values for the first and last item in the row
        var currentRow: Int = 0 // Tracks the current row
        attributes.forEach { layoutAttribute in
            
            // Each layoutAttribute represents its own item
            if layoutAttribute.frame.origin.y >= maxY {
                
                // This layoutAttribute represents the left-most item in the row
                leftMargin = leftPadding
                
                // Register its origin.x in rowSizes for use later
                if rowSizes.count == 0 {
                    // Add to first row
                    rowSizes = [[leftMargin, 0]]
                } else {
                    // Append a new row
                    rowSizes.append([leftMargin, 0])
                    currentRow += 1
                }
            }
            
            layoutAttribute.frame.origin.x = leftMargin
            
            leftMargin += layoutAttribute.frame.width + interItemSpacing
            maxY = max(layoutAttribute.frame.maxY, maxY)
            
            // Add right-most x value for last item in the row
            rowSizes[currentRow][1] = leftMargin - interItemSpacing
        }
        
        // At this point, all cells are left aligned
        // Reset tracking values and add extra left padding to center align entire row
        leftMargin = leftPadding
        maxY = -1.0
        currentRow = 0
        attributes.forEach { layoutAttribute in
            
            // Each layoutAttribute is its own item
            if layoutAttribute.frame.origin.y >= maxY {
                
                // This layoutAttribute represents the left-most item in the row
                leftMargin = leftPadding
                
                // Need to bump it up by an appended margin
                let rowWidth = rowSizes[currentRow][1] - rowSizes[currentRow][0] // last.x - first.x
                let appendedMargin = (collectionView!.frame.width - leftPadding  - rowWidth - leftPadding) / 2
                leftMargin += appendedMargin
                
                currentRow += 1
            }
            
            layoutAttribute.frame.origin.x = leftMargin
            
            leftMargin += layoutAttribute.frame.width + interItemSpacing
            maxY = max(layoutAttribute.frame.maxY, maxY)
        }
        
        return attributes
    }
}

CenterAlignedCollectionViewFlowLayout

Alex Koshy
la source
3
Cela a donné l'avertissement suivant, bien que cela se produise probablement parce que la sous-classe de disposition de flux xxxx.CustomCollectionViewFlowLayout modifie les attributs retournés par UICollectionViewFlowLayout sans les copier Pour corriger, j'ai utilisé le code de [ stackoverflow.com/a/36315664/1067951] guard let superArray = super.layoutAttributesForElementsInRect(rect) else { return nil } guard let attributes = NSArray(array: superArray, copyItems: true) as? [UICollectionViewLayoutAttributes] else { return nil }
Ram
Merci d'avoir posté. J'ai passé quelques jours à essayer d'y parvenir!
Mai Yang
Merci pour le travail et pour partager les résultats avec la communauté. J'ai mis à jour le code. Tout d'abord, j'ai ajouté les modifications recommandées par @Ram. Deuxièmement, je change interItemSpacingpour utiliser la valeur par défaut minimumInteritemSpacing.
kelin
@Ram J'ai un problème de Make a symbolic breakpoint at UICollectionViewFlowLayoutBreakForInvalidSizes pour attraper cela dans le débogueur. Le comportement de UICollectionViewFlowLayout n'est pas défini car: la largeur de l'élément doit être inférieure à la largeur de UICollectionView moins les valeurs de gauche et de droite des inserts de section, moins les valeurs de gauche et de droite des inserts de contenu
EI Captain v2.0
1
@Jack utilise comme ça dans viewDidLoad (). collectionViewName.collectionViewLayout = CenterAlignedCollectionViewFlowLayout ()
Mitesh Dobareeya
57

Les principales solutions ici ne fonctionnaient pas pour moi prêtes à l'emploi, j'ai donc proposé ceci qui devrait fonctionner pour toute vue de collection à défilement horizontal avec une disposition de flux et une seule section:

- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section
    {
    // Add inset to the collection view if there are not enough cells to fill the width.
    CGFloat cellSpacing = ((UICollectionViewFlowLayout *) collectionViewLayout).minimumLineSpacing;
    CGFloat cellWidth = ((UICollectionViewFlowLayout *) collectionViewLayout).itemSize.width;
    NSInteger cellCount = [collectionView numberOfItemsInSection:section];
    CGFloat inset = (collectionView.bounds.size.width - (cellCount * cellWidth) - ((cellCount - 1)*cellSpacing)) * 0.5;
    inset = MAX(inset, 0.0);
    return UIEdgeInsetsMake(0.0, inset, 0.0, 0.0);
    }
Siegfoult
la source
Vous devez multiplier cellSpacing par «cellCount - 1» et non par cellCount. Cela devient plus évident dans le cas où il n'y a qu'une seule cellule: il n'y a pas d'espace entre les cellules.
Fábio Oliveira
Et il devrait également y avoir un minimumInset à utiliser dans le 'inset = MAX (inset, 0.0);' ligne. Cela ressemblerait à 'inset = MAX (inset, minimumInset);'. Vous devez également appliquer ce même minimumInset sur le côté droit. Les vues de la collection sont tellement meilleures quand elles s'étendent jusqu'au bord :)
Fábio Oliveira
Cette solution et celle de @Sion fonctionnent également bien si la taille de la cellule est constante. Quelqu'un saurait-il comment obtenir la taille des cellules? cellForItemAtIndexPath et visibleCells semblent renvoyer des informations uniquement lorsque la collectionView est visible - et à ce stade du cycle de vie, ce n'est pas le cas.
Dan Loughney
@Siegfoult J'ai implémenté cela et cela a bien fonctionné jusqu'à ce que j'essaye de faire défiler vers la gauche, il revient au milieu.
Anirudha Mahale
34

Il est facile de calculer des inserts dynamiquement, ce code centrera toujours vos cellules:

NSInteger const SMEPGiPadViewControllerCellWidth = 332;

...

- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section
{

    NSInteger numberOfCells = self.view.frame.size.width / SMEPGiPadViewControllerCellWidth;
    NSInteger edgeInsets = (self.view.frame.size.width - (numberOfCells * SMEPGiPadViewControllerCellWidth)) / (numberOfCells + 1);

    return UIEdgeInsetsMake(0, edgeInsets, 0, edgeInsets);
}

- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
    [super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
    [self.collectionView.collectionViewLayout invalidateLayout];
}
Michal Zaborowski
la source
4
Quelle a été votre modification?
Siriss
4
@Siriss au lieu de SMEPGiPadViewControllerCellWidth, vous pouvez utiliser collectionViewLayout.itemSize.width, et c'était probablement lui une modification
Michal Zaborowski
21

Semblable à d'autres réponses, il s'agit d'une réponse dynamique qui devrait fonctionner pour les cellules de taille statique. La seule modification que j'ai apportée est que j'ai mis le rembourrage des deux côtés. Si je ne faisais pas cela, j'ai rencontré des problèmes.


Objectif c

- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewFlowLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section {

    NSInteger numberOfItems = [collectionView numberOfItemsInSection:section];
    CGFloat combinedItemWidth = (numberOfItems * collectionViewLayout.itemSize.width) + ((numberOfItems - 1) * collectionViewLayout.minimumInteritemSpacing);
    CGFloat padding = (collectionView.frame.size.width - combinedItemWidth) / 2;

    return UIEdgeInsetsMake(0, padding, 0, padding);
}

Rapide

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
    let flowLayout = collectionViewLayout as! UICollectionViewFlowLayout
    let numberOfItems = CGFloat(collectionView.numberOfItems(inSection: section))
    let combinedItemWidth = (numberOfItems * flowLayout.itemSize.width) + ((numberOfItems - 1)  * flowLayout.minimumInteritemSpacing)
    let padding = (collectionView.frame.width - combinedItemWidth) / 2
    return UIEdgeInsets(top: 0, left: padding, bottom: 0, right: padding)
}

En outre, si vous rencontrez toujours des problèmes, assurez-vous que vous définissez à la fois minimumInteritemSpacinget minimumLineSpacingsur les mêmes valeurs, car ces valeurs semblent être liées entre elles.

UICollectionViewFlowLayout *flowLayout = (UICollectionViewFlowLayout *)self.collectionView.collectionViewLayout;
flowLayout.minimumInteritemSpacing = 20.0;
flowLayout.minimumLineSpacing = 20.0;
kgaidis
la source
Juste planter pour moi
Supertecnoboff
Dans la collection d'objets ObjC, ViewLayout est utilisé directement. Il doit être converti en UICollectionViewFlowLayout pour pouvoir accéder à itemSize et minimumInteritemSpacing
buttcmd
19

Mettez ceci dans votre délégué de vue de collection. Il considère davantage les paramètres de mise en page de flux de base que les autres réponses et est donc plus générique.

- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section
{
    NSInteger cellCount = [collectionView.dataSource collectionView:collectionView numberOfItemsInSection:section];
    if( cellCount >0 )
    {
        CGFloat cellWidth = ((UICollectionViewFlowLayout*)collectionViewLayout).itemSize.width+((UICollectionViewFlowLayout*)collectionViewLayout).minimumInteritemSpacing;
        CGFloat totalCellWidth = cellWidth*cellCount + spacing*(cellCount-1);
        CGFloat contentWidth = collectionView.frame.size.width-collectionView.contentInset.left-collectionView.contentInset.right;
        if( totalCellWidth<contentWidth )
        {
            CGFloat padding = (contentWidth - totalCellWidth) / 2.0;
            return UIEdgeInsetsMake(0, padding, 0, padding);
        }
    }
    return UIEdgeInsetsZero;
}

version rapide (merci g0ld2k):

extension CommunityConnectViewController: UICollectionViewDelegateFlowLayout {
    func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAtIndex section: Int) -> UIEdgeInsets {

    // Translated from Objective-C version at: http://stackoverflow.com/a/27656363/309736

        let cellCount = CGFloat(viewModel.getNumOfItemsInSection(0))

        if cellCount > 0 {
            let flowLayout = collectionViewLayout as! UICollectionViewFlowLayout
            let cellWidth = flowLayout.itemSize.width + flowLayout.minimumInteritemSpacing
            let totalCellWidth = cellWidth*cellCount + spacing*(cellCount-1)
            let contentWidth = collectionView.frame.size.width - collectionView.contentInset.left - collectionView.contentInset.right

            if (totalCellWidth < contentWidth) {
                let padding = (contentWidth - totalCellWidth) / 2.0
                return UIEdgeInsetsMake(0, padding, 0, padding)
            }
        }

        return UIEdgeInsetsZero
    }
}
Bert
la source
3
En fait dans votre UICollectionViewDelegateFlowLayout. Merci, cela fonctionne pour un certain nombre de cellules qui sont une ou plusieurs lignes. Certaines des autres réponses ne le font pas.
Gene De Lisa
2
Converti ceci en Swift et cela fonctionne très bien! Version Swift disponible ici .
g0ld2k
1
Ce n'est pas tout à fait correct, totalCellWidthdevrait être cellWidth*cellCount + spacing*(cellCount-1).
James P
1
Après avoir essayé plusieurs de ces "solutions", c'est la seule que j'ai pu me mettre au travail correctement après l'avoir modifiée un peu pour swift3.0
kemicofa ghost
@rugdealer Voulez-vous modifier la réponse pour refléter vos changements swift3?
couchage le
11

Ma solution pour les cellules de vue de collection de taille statique qui doivent avoir un rembourrage à gauche et à droite-

func collectionView(collectionView: UICollectionView, 
layout collectionViewLayout: UICollectionViewLayout, 
insetForSectionAtIndex section: Int) -> UIEdgeInsets {

    let flowLayout = (collectionViewLayout as! UICollectionViewFlowLayout)
    let cellSpacing = flowLayout.minimumInteritemSpacing
    let cellWidth = flowLayout.itemSize.width
    let cellCount = CGFloat(collectionView.numberOfItemsInSection(section))

    let collectionViewWidth = collectionView.bounds.size.width

    let totalCellWidth = cellCount * cellWidth
    let totalCellSpacing = cellSpacing * (cellCount - 1)

    let totalCellsWidth = totalCellWidth + totalCellSpacing

    let edgeInsets = (collectionViewWidth - totalCellsWidth) / 2.0

    return edgeInsets > 0 ? UIEdgeInsetsMake(0, edgeInsets, 0, edgeInsets) : UIEdgeInsetsMake(0, cellSpacing, 0, cellSpacing)
}
Sagar Natekar
la source
9

J'ai une barre de balises dans mon application qui utilise un UICollectionView& UICollectionViewFlowLayout, avec une rangée de cellules alignées au centre.

Pour obtenir le retrait correct, vous soustrayez la largeur totale de toutes les cellules (y compris l'espacement) de la largeur de votre UICollectionView, et divisez par deux.

[........Collection View.........]
[..Cell..][..Cell..]
                   [____indent___] / 2

=

[_____][..Cell..][..Cell..][_____]

Le problème est cette fonction -

- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section;

est appelé avant ...

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

... vous ne pouvez donc pas parcourir vos cellules pour déterminer la largeur totale.

Au lieu de cela, vous devez calculer à nouveau la largeur de chaque cellule, dans mon cas, j'utilise [NSString sizeWithFont: ... ]car mes largeurs de cellule sont déterminées par le UILabel lui-même.

- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section
{
    CGFloat rightEdge = 0;
    CGFloat interItemSpacing = [(UICollectionViewFlowLayout*)collectionViewLayout minimumInteritemSpacing];

    for(NSString * tag in _tags)
        rightEdge += [tag sizeWithFont:[UIFont systemFontOfSize:14]].width+interItemSpacing;

    // To center the inter spacing too
    rightEdge -= interSpacing/2;

    // Calculate the inset
    CGFloat inset = collectionView.frame.size.width-rightEdge;

    // Only center align if the inset is greater than 0
    // That means that the total width of the cells is less than the width of the collection view and need to be aligned to the center.
    // Otherwise let them align left with no indent.

    if(inset > 0)
        return UIEdgeInsetsMake(0, inset/2, 0, 0);
    else
        return UIEdgeInsetsMake(0, 0, 0, 0);
}
Siôn
la source
Comment obtenez-vous interSpacing?
Drux
interSpacingest juste une constante dans mon code, qui détermine l'espace que j'ai entre UICollectionViewCells
Siôn
1
Mais cet espace peut varier: FWIK vous ne pouvez supposer / énoncer que la valeur minimale.
Drux
8

Dans Swift, ce qui suit distribuera les cellules uniformément en appliquant la bonne quantité de remplissage sur les côtés de chaque cellule. Bien sûr, vous devez d'abord connaître / définir la largeur de votre cellule.

func collectionView(collectionView: UICollectionView,
    layout collectionViewLayout: UICollectionViewLayout,
    insetForSectionAtIndex section: Int) -> UIEdgeInsets {

        var cellWidth : CGFloat = 110;

        var numberOfCells = floor(self.view.frame.size.width / cellWidth);
        var edgeInsets = (self.view.frame.size.width - (numberOfCells * cellWidth)) / (numberOfCells + 1);

        return UIEdgeInsetsMake(0, edgeInsets, 60.0, edgeInsets);
}
Dale Clifford
la source
Pour ajouter un espacement entre les lignes, changez 0 au lieu de 60,0
oscar castellon
8

Swift 2.0 fonctionne bien pour moi!

 func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAtIndex section: Int) -> UIEdgeInsets {
        let edgeInsets = (screenWight - (CGFloat(elements.count) * 50) - (CGFloat(elements.count) * 10)) / 2
        return UIEdgeInsetsMake(0, edgeInsets, 0, 0);
    }

Où: screenWight : essentiellement la largeur de ma collection (largeur totale de l'écran) - J'ai créé des constantes: laissez screenWight: CGFloat = UIScreen.mainScreen (). Bounds.width parce que self.view.bounds montre à chaque fois 600 - coz des éléments SizeClasses - tableau de cellules 50 - ma largeur de cellule manuelle 10 - ma distance entre les cellules

Dmitrii Z
la source
1
Vous pouvez utiliser collectionView.frame.size.width au lieu de screenWight, juste au cas où vous voudriez redimensionner le UICollectionView
Oscar
2

Vous ne savez pas si c'est nouveau dans Xcode 5 ou non, mais vous pouvez maintenant ouvrir l'inspecteur de taille via Interface Builder et y définir un encart. Cela vous évitera d'avoir à écrire du code personnalisé pour le faire à votre place et devrait augmenter considérablement la vitesse à laquelle vous trouvez les décalages appropriés.

Sandy Chapman
la source
1

Une autre façon de procéder consiste à définir le collectionView.center.x, comme ceci:

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()

    if let
        collectionView = collectionView,
        layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout
    {
        collectionView.center.x = view.center.x + layout.sectionInset.right / 2.0
    }
}

Dans ce cas, ne respectant que le bon encart qui fonctionne pour moi.

Paul Peelen
la source
1

Sur la base de la réponse user3676011, je peux en suggérer une plus détaillée avec une petite correction. Cette solution fonctionne très bien sur Swift 2.0 .

enum CollectionViewContentPosition {
    case Left
    case Center
}

var collectionViewContentPosition: CollectionViewContentPosition = .Left

func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout,
    insetForSectionAtIndex section: Int) -> UIEdgeInsets {

    if collectionViewContentPosition == .Left {
        return UIEdgeInsetsZero
    }

    // Center collectionView content

    let itemsCount: CGFloat = CGFloat(collectionView.numberOfItemsInSection(section))
    let collectionViewWidth: CGFloat = collectionView.bounds.width

    let itemWidth: CGFloat = 40.0
    let itemsMargin: CGFloat = 10.0

    let edgeInsets = (collectionViewWidth - (itemsCount * itemWidth) - ((itemsCount-1) * itemsMargin)) / 2

    return UIEdgeInsetsMake(0, edgeInsets, 0, 0)
}

Il y avait un problème dans

(CGFloat (nombre d'éléments) * 10))

où devrait être -1mentionné plus .

Novamax
la source
1

C'est ainsi que j'ai obtenu une vue de collection alignée au centre avec un espacement Interitem fixe

#define maxInteritemSpacing 6
#define minLineSpacing 3

@interface MyFlowLayout()<UICollectionViewDelegateFlowLayout>

@end

@implementation MyFlowLayout

- (instancetype)init
{
    self = [super init];
    if (self) {
        self.minimumInteritemSpacing = 3;
        self.minimumLineSpacing = minLineSpacing;
        self.scrollDirection = UICollectionViewScrollDirectionVertical;
    }
    return self;
}

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

    if (answer.count > 0) {
        NSMutableArray<NSMutableArray<UICollectionViewLayoutAttributes *> *> *rows = [NSMutableArray array];
        CGFloat maxY = -1.0f;
        NSInteger currentRow = 0;

        //add first item to row and set its maxY
        [rows addObject:[@[answer[0]] mutableCopy]];
        maxY = CGRectGetMaxY(((UICollectionViewLayoutAttributes *)answer[0]).frame);

        for(int i = 1; i < [answer count]; ++i) {
            //handle maximum spacing
            UICollectionViewLayoutAttributes *currentLayoutAttributes = answer[i];
            UICollectionViewLayoutAttributes *prevLayoutAttributes = answer[i - 1];
            NSInteger maximumSpacing = maxInteritemSpacing;
            NSInteger origin = CGRectGetMaxX(prevLayoutAttributes.frame);

            if(origin + maximumSpacing + currentLayoutAttributes.frame.size.width < self.collectionViewContentSize.width) {
                CGRect frame = currentLayoutAttributes.frame;
                frame.origin.x = origin + maximumSpacing;
                currentLayoutAttributes.frame = frame;
            }

            //handle center align
            CGFloat currentY = currentLayoutAttributes.frame.origin.y;
            if (currentY >= maxY) {
                [self shiftRowToCenterForCurrentRow:rows[currentRow]];

                //next row
                [rows addObject:[@[currentLayoutAttributes] mutableCopy]];
                currentRow++;
            }
            else {
                //same row
                [rows[currentRow] addObject:currentLayoutAttributes];
            }

            maxY = MAX(CGRectGetMaxY(currentLayoutAttributes.frame), maxY);
        }

        //mark sure to shift 1 row items
        if (currentRow == 0) {
            [self shiftRowToCenterForCurrentRow:rows[currentRow]];
        }
    }

    return answer;
}

- (void)shiftRowToCenterForCurrentRow:(NSMutableArray<UICollectionViewLayoutAttributes *> *)currentRow
{
    //shift row to center
    CGFloat endingX = CGRectGetMaxX(currentRow.lastObject.frame);
    CGFloat shiftX = (self.collectionViewContentSize.width - endingX) / 2.f;
    for (UICollectionViewLayoutAttributes *attr in currentRow) {
        CGRect shiftedFrame = attr.frame;
        shiftedFrame.origin.x += shiftX;
        attr.frame = shiftedFrame;
    }
}

@end
Zhong Huiwen
la source
1

Version de travail de la réponse Objective C de kgaidis utilisant Swift 3.0:

let flow = UICollectionViewFlowLayout()

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {        
    let numberOfItems = collectionView.numberOfItems(inSection: 0)
    let combinedItemWidth:CGFloat = (CGFloat(numberOfItems) * flow.itemSize.width) + ((CGFloat(numberOfItems) - 1) * flow.minimumInteritemSpacing)
    let padding = (collectionView.frame.size.width - combinedItemWidth) / 2

    return UIEdgeInsetsMake(0, padding, 0, padding)
}
RJH
la source
1

Voici ma solution avec quelques hypothèses:

  • il n'y a qu'une seule section
  • les inserts gauche et droit sont égaux
  • la hauteur de la cellule est la même

N'hésitez pas à vous adapter à vos besoins.

Disposition centrée avec largeur de cellule variable:

protocol HACenteredLayoutDelegate: UICollectionViewDataSource {
    func getCollectionView() -> UICollectionView
    func sizeOfCell(at index: IndexPath) -> CGSize
    func contentInsets() -> UIEdgeInsets
}

class HACenteredLayout: UICollectionViewFlowLayout {
    weak var delegate: HACenteredLayoutDelegate?
    private var cache = [UICollectionViewLayoutAttributes]()
    private var contentSize = CGSize.zero
    override var collectionViewContentSize: CGSize { return self.contentSize }

    required init(delegate: HACenteredLayoutDelegate) {
        self.delegate = delegate
        super.init()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    override func invalidateLayout() {
        cache.removeAll()
        super.invalidateLayout()
    }

    override func prepare() {
        if cache.isEmpty && self.delegate != nil && self.delegate!.collectionView(self.delegate!.getCollectionView(), numberOfItemsInSection: 0) > 0 {
            let insets = self.delegate?.contentInsets() ?? UIEdgeInsets.zero
            var rows: [(width: CGFloat, count: Int)] = [(0, 0)]
            let viewWidth: CGFloat = UIScreen.main.bounds.width
            var y = insets.top
            var unmodifiedIndexes = [IndexPath]()
            for itemNumber in 0 ..< self.delegate!.collectionView(self.delegate!.getCollectionView(), numberOfItemsInSection: 0) {
                let indexPath = IndexPath(item: itemNumber, section: 0)
                let cellSize = self.delegate!.sizeOfCell(at: indexPath)
                let potentialRowWidth = rows.last!.width + (rows.last!.count > 0 ? self.minimumInteritemSpacing : 0) + cellSize.width + insets.right + insets.left
                if potentialRowWidth > viewWidth {
                    let leftOverSpace = max((viewWidth - rows[rows.count - 1].width)/2, insets.left)
                    for i in unmodifiedIndexes {
                        self.cache[i.item].frame.origin.x += leftOverSpace
                    }
                    unmodifiedIndexes = []
                    rows.append((0, 0))
                    y += cellSize.height + self.minimumLineSpacing
                }
                unmodifiedIndexes.append(indexPath)
                let attribute = UICollectionViewLayoutAttributes(forCellWith: indexPath)
                rows[rows.count - 1].count += 1
                rows[rows.count - 1].width += rows[rows.count - 1].count > 1 ? self.minimumInteritemSpacing : 0
                attribute.frame = CGRect(x: rows[rows.count - 1].width, y: y, width: cellSize.width, height: cellSize.height)
                rows[rows.count - 1].width += cellSize.width
                cache.append(attribute)
            }
            let leftOverSpace = max((viewWidth - rows[rows.count - 1].width)/2, insets.left)
            for i in unmodifiedIndexes {
                self.cache[i.item].frame.origin.x += leftOverSpace
            }
            self.contentSize = CGSize(width: viewWidth, height: y + self.delegate!.sizeOfCell(at: IndexPath(item: 0, section: 0)).height + insets.bottom)
        }
    }

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

        for attributes in cache {
            if attributes.frame.intersects(rect) {
                layoutAttributes.append(attributes)
            }
        }
        return layoutAttributes
    }

    override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        if indexPath.item < self.cache.count {
            return self.cache[indexPath.item]
        }
        return nil
    }
}

Résultat:

entrez la description de l'image ici

Whitney Foster
la source
0

Voici comment vous pouvez le faire et cela fonctionne bien

func refreshCollectionView(_ count: Int) {
    let collectionViewHeight = collectionView.bounds.height
    let collectionViewWidth = collectionView.bounds.width
    let numberOfItemsThatCanInCollectionView = Int(collectionViewWidth / collectionViewHeight)
    if numberOfItemsThatCanInCollectionView > count {
        let totalCellWidth = collectionViewHeight * CGFloat(count)
        let totalSpacingWidth: CGFloat = CGFloat(count) * (CGFloat(count) - 1)
        // leftInset, rightInset are the global variables which I am passing to the below function
        leftInset = (collectionViewWidth - CGFloat(totalCellWidth + totalSpacingWidth)) / 2;
        rightInset = -leftInset
    } else {
        leftInset = 0.0
        rightInset = -collectionViewHeight
    }
    collectionView.reloadData()
}

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
    return UIEdgeInsetsMake(0, leftInset, 0, rightInset)
}
Anirudha Mahale
la source