¿Cómo centrar la alineación de las celdas de un UICollectionView?

99

Actualmente estoy usando UICollectionViewpara la cuadrícula de la interfaz de usuario y funciona bien. Sin embargo, me gustaría habilitar el desplazamiento horizontal. La cuadrícula admite 8 elementos por página y cuando el número total de elementos es, digamos 4, así es como se deben organizar los elementos con la dirección de desplazamiento horizontal habilitada:

0 0 x x
0 0 x x

Aquí 0 -> Elemento de colección yx -> Celdas vacías

¿Hay alguna manera de alinearlos en el centro como:

x 0 0 x
x 0 0 x

¿Para que el contenido se vea más limpio?

Además, el arreglo a continuación también podría ser una solución que estoy esperando.

0 0 0 0
x x x x
RVN
fuente

Respuestas:

80

Creo que puede lograr el aspecto de una sola línea implementando algo como esto:

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

Tendrá que jugar con ese número para descubrir cómo forzar el contenido en una sola línea. El primer 0 es el argumento del borde superior, también puede ajustarlo si desea centrar el contenido verticalmente en la pantalla.

rdelmar
fuente
1
Esta es una buena solución gracias, sin embargo, esto requiere algunos cálculos adicionales de mi parte.
RVN
¿Cómo se calculan estos valores?
jjxtra
7
PARA calcular dinámicamente recuadros: CGFloat leftInset = (collectionView.frame.size.width-40 * [collectionView numberOfItemsInSection: section]) / 2; 40 = tamaño de la celda
Abduliam Rehmanius
1
@AbduliamRehmanius Pero esto supone que las celdas no están espaciadas horizontalmente. Normalmente lo son. Y parece poco sencillo determinar los espacios reales porque minimumInteritemSpacingsolo indica su mínimo.
Drux
5
En realidad debería serlo return UIEdgeInsetsMake(0, 100, 0, 100);. Si hay espacio adicional, la vista de colección expandirá el espaciado intermedio, por lo que debe asegurarse de que LeftInset + CellWidth * Ncells + CellSpacing * (Ncells-1) + RightInset = CollectionViewWidth
vish
82

Para aquellos que buscan una solución para las celdas de vista de colección de ancho dinámico alineadas al centro , como yo, terminé modificando la respuesta de Angel para una versión alineada a la izquierda para crear una subclase alineada al centro para 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
fuente
3
Esto dio la siguiente advertencia, aunque es probable que esto ocurra porque la subclase de diseño de flujo xxxx.CustomCollectionViewFlowLayout está modificando los atributos devueltos por UICollectionViewFlowLayout sin copiarlos. Para corregirlos, utilicé el código 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
Gracias por tu publicación. ¡Pasé un par de días tratando de lograr esto!
Yang de mayo
Gracias por el trabajo y por compartir los resultados con la comunidad. Actualicé el código. En primer lugar, agregué los cambios recomendados por @Ram. En segundo lugar, cambio interItemSpacingpara usar el predeterminado minimumInteritemSpacing.
Kelin
@Ram Tengo un problema de Hacer un punto de interrupción simbólico en UICollectionViewFlowLayoutBreakForInvalidSizes para detectar esto en el depurador. El comportamiento de UICollectionViewFlowLayout no está definido porque: el ancho del elemento debe ser menor que el ancho de UICollectionView menos la sección inserta los valores izquierdo y derecho, menos el contenido inserta los valores izquierdo y derecho
EI Captain v2.0
1
@Jack usa así en viewDidLoad (). collectionViewName.collectionViewLayout = CenterAlignedCollectionViewFlowLayout ()
Mitesh Dobareeya
57

Las principales soluciones aquí no me funcionaron de inmediato, por lo que se me ocurrió esto, que debería funcionar para cualquier vista de colección de desplazamiento horizontal con diseño de flujo y solo una sección:

- (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
fuente
Debe multiplicar cellSpacing por 'cellCount - 1' y no cellCount. Se hace más evidente en el caso en el que solo hay una celda: no hay espacio entre las celdas.
Fábio Oliveira
Y también debería haber un mínimoInset para ser utilizado en el 'inset = MAX (inset, 0.0);' línea. Se vería como 'inset = MAX (inset, minimumInset);'. También debe aplicar ese mismo conjunto mínimo en el lado derecho. Las vistas de la colección se ven mucho mejor cuando se extienden hasta el borde :)
Fábio Oliveira
Tanto esta solución como la de @Sion funcionan igual de bien si el tamaño de la celda es constante. ¿Alguien sabría cómo obtener el tamaño de las células? cellForItemAtIndexPath y visibleCells solo parecen devolver información cuando collectionView es visible, y en este punto del ciclo de vida, no lo es.
Dan Loughney
@Siegfoult Implementé esto y funcionó bien hasta que intenté desplazarme hacia la izquierda, vuelve al medio.
Anirudha Mahale
34

Es fácil calcular inserciones dinámicamente, este código siempre centrará sus celdas:

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
fuente
4
¿Cuál fue tu modificación?
Siriss
4
@Siriss en lugar de SMEPGiPadViewControllerCellWidth, puede usar collectionViewLayout.itemSize.width, y probablemente esta fue su modificación
Michal Zaborowski
21

Al igual que en otras respuestas, esta es una respuesta dinámica que debería funcionar para celdas de tamaño estático. La única modificación que hice es que puse el acolchado en ambos lados. Si no hice esto, tuve problemas.


C objetivo

- (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);
}

Rápido

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

Además, si aún tiene problemas, asegúrese de establecer ambos valores minimumInteritemSpacingy minimumLineSpacinglos mismos, ya que estos valores parecen estar interrelacionados.

UICollectionViewFlowLayout *flowLayout = (UICollectionViewFlowLayout *)self.collectionView.collectionViewLayout;
flowLayout.minimumInteritemSpacing = 20.0;
flowLayout.minimumLineSpacing = 20.0;
kgaidis
fuente
Simplemente se bloquea para mí
Supertecnoboff
En ObjC, la colección de objetosViewLayout se usa directamente. Debe enviarse a UICollectionViewFlowLayout para poder acceder a itemSize y minimumInteritemSpacing
buttcmd
19

Pon esto en tu delegado de vista de colección. Considera más la configuración de diseño de flujo básico que las otras respuestas y, por lo tanto, es más genérico.

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

versión rápida (gracias 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
fuente
3
En realidad, en su UICollectionViewDelegateFlowLayout. Gracias, esto funciona para varias celdas que son de una o varias filas. Algunas de las otras respuestas no lo hacen.
Gene De Lisa
2
¡Convertí esto a Swift y funciona muy bien! Versión rápida disponible aquí .
g0ld2k
1
Esto no es del todo correcto, totalCellWidthdebería serlo cellWidth*cellCount + spacing*(cellCount-1).
James P
1
Después de probar varias de estas "soluciones", esta fue la única que pude hacer funcionar correctamente después de modificarlo un poco para swift3.0
kemicofa ghost
@rugdealer ¿Quieres modificar la respuesta para reflejar tus cambios de swift3?
bert
11

Mi solución para celdas de vista de colección de tamaño estático que deben tener relleno a izquierda y derecha

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
fuente
9

Tengo una barra de etiquetas en mi aplicación que usa un UICollectionView& UICollectionViewFlowLayout, con una fila de celdas alineadas en el centro.

Para obtener la sangría correcta, resta el ancho total de todas las celdas (incluido el espaciado) del ancho de tu UICollectionViewy divídelo por dos.

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

=

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

El problema es esta función:

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

se llama antes ...

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

... por lo que no puede iterar sobre sus celdas para determinar el ancho total.

En su lugar, debe calcular el ancho de cada celda nuevamente, en mi caso lo uso [NSString sizeWithFont: ... ]ya que los anchos de mi celda están determinados por el propio UILabel.

- (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
fuente
¿Cómo se obtiene interSpacing?
Drux
interSpacinges solo una constante en mi código, que determina el espacio que tengo entre UICollectionViewCells
Siôn
1
Pero este espacio puede variar: FWIK solo puede asumir / indicar el valor mínimo.
Drux
8

En Swift, lo siguiente distribuirá las celdas de manera uniforme aplicando la cantidad correcta de relleno en los lados de cada celda. Por supuesto, primero debe conocer / configurar el ancho de su celda.

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
fuente
Para agregar espacio entre filas, cambie 0 en lugar de 60.0
oscar castellon
8

¡Swift 2.0 funciona bien para mí!

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

Dónde: screenWight : básicamente, su anchura de mi colección (ancho de pantalla completa) - Me hizo constantes: dejado screenWight:. CGFloat = UIScreen.mainScreen () bounds.width porque self.view.bounds espectáculos cada vez 600 - coz de SizeClasses elementos - matriz de celdas 50 - mi ancho de celda manual 10 - mi distancia entre celdas

Dmitrii Z
fuente
1
Puede usar collectionView.frame.size.width en lugar de screenWight, en caso de que desee cambiar el tamaño de UICollectionView
Oscar
2

No estoy seguro de si esto es nuevo en Xcode 5 o no, pero ahora puede abrir el Inspector de tamaño a través de Interface Builder y establecer un recuadro allí. Eso evitará que tenga que escribir código personalizado para hacer esto por usted y debería aumentar drásticamente la velocidad a la que encuentra las compensaciones adecuadas.

Sandy Chapman
fuente
1

Otra forma de hacer esto es configurando collectionView.center.x, así:

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

En este caso, solo respetando el recuadro correcto que me funciona.

Paul Peelen
fuente
1

Según la respuesta del usuario 3676011, puedo sugerir una más detallada con una pequeña corrección. Esta solución funciona muy bien en 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)
}

Hubo un problema en

(CGFloat (recuento de elementos) * 10))

donde debe -1mencionarse adicionalmente .

Novamax
fuente
1

Así es como obtuve una vista de colección que estaba alineada en el centro con un espaciado fijo entre elementos

#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
fuente
1

Versión funcional de la respuesta de Objective C de kgaidis usando 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
fuente
1

Aquí está mi solución con algunas suposiciones:

  • solo hay una sección
  • Las inserciones izquierda y derecha son iguales
  • la altura de la celda es la misma

Siéntase libre de adaptarse a sus necesidades.

Diseño centrado con ancho de celda 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
    }
}

Resultado:

ingrese la descripción de la imagen aquí

Whitney Foster
fuente
0

Así es como puede hacerlo y funciona 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
fuente