Como centralizar o alinhamento das células de um UICollectionView?

99

Estou usando atualmente UICollectionViewpara a grade de interface do usuário e funciona bem. No entanto, gostaria de habilitar a rolagem horizontal. A grade suporta 8 itens por página e quando o número total de itens é, digamos 4, é assim que os itens devem ser organizados com a direção de rolagem horizontal ativada:

0 0 x x
0 0 x x

Aqui 0 -> Item de coleção ex -> Células vazias

Existe uma maneira de torná-los alinhados ao centro como:

x 0 0 x
x 0 0 x

Para que o conteúdo fique mais limpo?

Além disso, o arranjo abaixo também pode ser uma solução que estou esperando.

0 0 0 0
x x x x
RVN
fonte

Respostas:

80

Acho que você pode conseguir a aparência de linha única implementando algo como isto:

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

Você terá que brincar com esse número para descobrir como forçar o conteúdo em uma única linha. O primeiro 0 é o argumento da borda superior, você também pode ajustá-lo se quiser centralizar o conteúdo verticalmente na tela.

rdelmar
fonte
1
Esta é uma boa solução, obrigado, no entanto, isso requer alguns cálculos extras da minha parte.
RVN
Como você calcula esses valores?
jjxtra
7
PARA calcular inserções dinamicamente: CGFloat leftInset = (collectionView.frame.size.width-40 * [collectionView numberOfItemsInSection: section]) / 2; 40 = tamanho da célula
Abduliam Rehmanius
1
@AbduliamRehmanius Mas isso assume que as células não estão espaçadas horizontalmente. Normalmente são. E não parece simples determinar os espaços reais porque minimumInteritemSpacingindica apenas o mínimo.
Drux de
5
Na verdade, deveria ser return UIEdgeInsetsMake(0, 100, 0, 100);. Se houver espaço extra, a visualização da coleção irá expandir o espaçamento provisório, então você precisa ter certeza de LeftInset + CellWidth * Ncells + CellSpacing * (Ncells-1) + RightInset = CollectionViewWidth
vish
83

Para aqueles que procuram uma solução para células de visualização de coleção de largura dinâmica e alinhadas ao centro , como eu estava, acabei modificando a resposta de Angel para uma versão alinhada à esquerda para criar uma subclasse alinhada ao centro paraUICollectionViewFlowLayout .

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
fonte
3
Isso gerou o seguinte aviso, embora provavelmente esteja ocorrendo porque a subclasse de layout de fluxo xxxx.CustomCollectionViewFlowLayout está modificando os atributos retornados por UICollectionViewFlowLayout sem copiá-los. Para corrigir, usei o 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
Obrigado por postar. Passei alguns dias tentando conseguir isso!
Maio Yang
Obrigado pelo trabalho e por compartilhar os resultados com a comunidade. Eu atualizei o código. Em primeiro lugar, adicionei as alterações recomendadas por @Ram. Em segundo lugar, mudo interItemSpacingpara usar o padrão minimumInteritemSpacing.
kelin
@Ram Tenho um problema de fazer um ponto de interrupção simbólico em UICollectionViewFlowLayoutBreakForInvalidSizes para detectar isso no depurador. O comportamento do UICollectionViewFlowLayout não está definido porque: a largura do item deve ser menor que a largura do UICollectionView menos os valores da seção esquerda e direita, menos os valores das inserções de conteúdo à esquerda e direita
EI Captain v2.0
1
@Jack use assim em viewDidLoad (). collectionViewName.collectionViewLayout = CenterAlignedCollectionViewFlowLayout ()
Mitesh Dobareeya
57

As principais soluções aqui não funcionaram para mim prontamente, então eu vim com isso que deve funcionar para qualquer exibição de coleção de rolagem horizontal com layout de fluxo e apenas uma seção:

- (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
fonte
Você deve multiplicar cellSpacing por 'cellCount - 1' e não cellCount. Torna-se mais evidente no caso de haver apenas uma célula: não há espaço entre as células.
Fábio Oliveira
E também deve haver um minimumInset a ser usado em 'inset = MAX (inset, 0.0);' linha. Seria semelhante a 'inset = MAX (inset, minimumInset);'. Você deve aplicar esse mesmo MinimumInset no lado direito também. As visualizações das coleções ficam muito melhores quando se estendem até a borda :)
Fábio Oliveira
Esta solução e a de @Sion funcionam igualmente bem se o tamanho da célula for constante. Alguém saberia saber o tamanho das células? cellForItemAtIndexPath e visibleCells só parecem retornar informações quando a collectionView está visível - e neste ponto do ciclo de vida, não é.
Dan Loughney,
@Siegfoult Eu implementei isso e funcionou bem até que tentei rolar para a esquerda e voltou para o meio.
Anirudha Mahale
34

É fácil calcular inserções dinamicamente, este código sempre centralizará suas células:

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
fonte
4
Qual foi a sua modificação?
Siriss
4
@Siriss em vez de SMEPGiPadViewControllerCellWidth você pode usar collectionViewLayout.itemSize.width, e esta provavelmente foi a modificação dele
Michal Zaborowski
21

Semelhante a outras respostas, esta é uma resposta dinâmica que deve funcionar para células de tamanho estático. A única modificação que fiz foi colocar o enchimento em ambos os lados. Se eu não fizesse isso, tive problemas.


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

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

Além disso, se ainda estiver tendo problemas, certifique-se de definir os valores minimumInteritemSpacinge minimumLineSpacingcom os mesmos, pois esses valores parecem estar relacionados.

UICollectionViewFlowLayout *flowLayout = (UICollectionViewFlowLayout *)self.collectionView.collectionViewLayout;
flowLayout.minimumInteritemSpacing = 20.0;
flowLayout.minimumLineSpacing = 20.0;
kgaidis
fonte
Apenas trava para mim
Supertecnoboff
No objeto ObjC, o collectionViewLayout é usado diretamente. Deve ser convertido para UICollectionViewFlowLayout para poder acessar itemSize e minimumInteritemSpacing
buttcmd
19

Coloque isso em seu delegado de visualização de coleção. Ele considera mais as configurações básicas do layout de fluxo do que as outras respostas e, portanto, é mais 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;
}

versão rápida (obrigado 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
fonte
3
Na verdade, em seu UICollectionViewDelegateFlowLayout. Obrigado, isso funciona para várias células que são uma linha ou várias linhas. Algumas das outras respostas não.
Gene De Lisa de
2
Convertei isso para o Swift e funciona muito bem! Versão rápida disponível aqui .
g0ld2k
1
Isso não é totalmente correto, totalCellWidthdeveria estar cellWidth*cellCount + spacing*(cellCount-1).
James P
1
Depois de tentar várias dessas "soluções", essa foi a única que consegui fazer funcionar corretamente depois de modificá-la um pouco para swift3.0
kemicofa ghost
@rugdealer Você deseja modificar a resposta para refletir suas mudanças no swift3?
bert de
11

Minha solução para células de visualização de coleção de tamanho estático que precisam ter preenchimento à esquerda e à direita

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

Eu tenho uma barra de tags em meu aplicativo que usa um UICollectionView& UICollectionViewFlowLayout, com uma linha de células alinhada ao centro.

Para obter o recuo correto, você subtrai a largura total de todas as células (incluindo o espaçamento) da largura da sua UICollectionViewe divide por dois.

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

=

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

O problema é esta função -

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

é chamado antes ...

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

... então você não pode iterar em suas células para determinar a largura total.

Em vez disso, você precisa calcular a largura de cada célula novamente, no meu caso eu uso, [NSString sizeWithFont: ... ]pois as larguras das minhas células são determinadas pelo próprio 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
fonte
Como você consegue interSpacing?
Drux de
interSpacingé apenas uma constante em meu código, que determina o espaço que tenho entre UICollectionViewCells
Siôn
1
Mas este espaço pode variar: FWIK você só pode assumir / declarar o valor mínimo.
Drux
8

No Swift, o seguinte distribuirá as células uniformemente, aplicando a quantidade correta de preenchimento nas laterais de cada célula. Claro, você precisa saber / definir a largura da sua célula primeiro.

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
fonte
Para adicionar espaçamento entre linhas, altere 0 em vez de 60,0
oscar castellon
8

Swift 2.0 funciona bem para mim!

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

Onde: screenWight : basicamente é a largura da minha coleção (largura da tela inteira) - Fiz constantes: let screenWight: CGFloat = UIScreen.mainScreen (). Bounds.width porque self.view.bounds mostra 600 a cada vez - por causa dos elementos SizeClasses - matriz de células 50 - minha largura manual de célula 10 - minha distância entre as células

Dmitrii Z
fonte
1
Você poderia usar collectionView.frame.size.width em vez de screenWight, apenas no caso de você querer redimensionar o UICollectionView
Oscar de
2

Não tenho certeza se isso é novo no Xcode 5 ou não, mas agora você pode abrir o Inspetor de tamanho por meio do Interface Builder e definir uma inserção lá. Isso evitará que você tenha que escrever um código personalizado para fazer isso para você e deve aumentar drasticamente a velocidade com que você encontra os deslocamentos adequados.

Sandy Chapman
fonte
1

Outra maneira de fazer isso é definir o collectionView.center.x, assim:

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

Neste caso, apenas respeitando o inset certo que funciona para mim.

Paul Peelen
fonte
1

Com base na resposta do usuário 3676011, posso sugerir um mais detalhado com uma pequena correção. Esta solução funciona muito bem no 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)
}

Houve um problema em

(CGFloat (elements.count) * 10))

onde deve ser -1mencionado adicional .

Novamax
fonte
1

Foi assim que obtive uma visão da coleção alinhada ao centro com um espaçamento fixo entre os itens

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

Versão de trabalho da resposta Objective C do 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
fonte
1

Aqui está minha solução com algumas suposições:

  • há apenas uma seção
  • inserções esquerda e direita são iguais
  • a altura da célula é a mesma

Sinta-se à vontade para se adaptar às suas necessidades.

Layout centralizado com largura de célula variável:

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:

insira a descrição da imagem aqui

Whitney Foster
fonte
0

Aqui está como você pode fazer isso e funciona bem

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
fonte