Wie zentriere ich die Zellen einer UICollectionView?

99

Ich verwende derzeit UICollectionViewfür das Benutzeroberflächenraster, und es funktioniert gut. Ich möchte jedoch das horizontale Scrollen aktivieren. Das Raster unterstützt 8 Elemente pro Seite. Wenn die Gesamtzahl der Elemente beispielsweise 4 beträgt, sollten die Elemente so angeordnet werden, dass die horizontale Bildlaufrichtung aktiviert ist:

0 0 x x
0 0 x x

Hier 0 -> Sammlungsgegenstand und x -> Leere Zellen

Gibt es eine Möglichkeit, sie zentriert auszurichten wie:

x 0 0 x
x 0 0 x

Damit der Inhalt sauberer aussieht?

Auch die folgende Anordnung könnte auch eine Lösung sein, die ich erwarte.

0 0 0 0
x x x x
RVN
quelle

Antworten:

80

Ich denke, Sie können den Single-Line-Look erzielen, indem Sie Folgendes implementieren:

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

Sie müssen mit dieser Zahl herumspielen, um herauszufinden, wie Sie den Inhalt in eine einzelne Zeile zwingen können. Die erste 0 ist das Argument der oberen Kante. Sie können dieses Argument auch anpassen, wenn Sie den Inhalt vertikal auf dem Bildschirm zentrieren möchten.

rdelmar
quelle
1
Dies ist eine gute Lösung, danke, aber dies erfordert einige zusätzliche Berechnungen auf meiner Seite.
RVN
Wie berechnen Sie diese Werte?
jjxtra
7
Um Einfügungen dynamisch zu berechnen: CGFloat leftInset = (collectionView.frame.size.width-40 * [collectionView numberOfItemsInSection: section]) / 2; 40 = Größe der Zelle
Abduliam Rehmanius
1
@AbduliamRehmanius Dies setzt jedoch voraus, dass die Zellen nicht horizontal beabstandet sind. Normalerweise sind sie es. Und es scheint unkompliziert, tatsächliche Räume zu bestimmen, da minimumInteritemSpacingnur deren Minimum angegeben wird.
Drux
5
Es sollte eigentlich sein return UIEdgeInsetsMake(0, 100, 0, 100);. Wenn zusätzlicher Speicherplatz vorhanden ist, erweitert die Sammlungsansicht den Zwischenabstand. Stellen Sie daher sicher, dass LeftInset + CellWidth * Ncells + CellSpacing * (Ncells-1) + RightInset = CollectionViewWidth
vish
82

Für diejenigen, die nach einer Lösung für mittig ausgerichtete Sammlungsansichtszellen mit dynamischer Breite suchen , habe ich letztendlich Angels Antwort für eine links ausgerichtete Version geändert , um eine mittig ausgerichtete Unterklasse für zu erstellen 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
quelle
3
Dies gab die folgende Warnung aus, obwohl dies wahrscheinlich auftritt, weil die Flow-Layout-Unterklasse xxxx.CustomCollectionViewFlowLayout die von UICollectionViewFlowLayout zurückgegebenen Attribute ändert, ohne sie zu kopieren. Zur Korrektur habe ich den Code von [ 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
Vielen Dank für die Veröffentlichung. Verbrachte ein paar Tage damit, dies zu erreichen!
Mai Yang
Vielen Dank für Ihre Arbeit und für das Teilen der Ergebnisse mit der Community. Ich habe den Code aktualisiert. Zunächst habe ich die von @Ram empfohlenen Änderungen hinzugefügt. Zweitens ändere ich interItemSpacingdie Standardeinstellung minimumInteritemSpacing.
Kelin
@Ram Ich habe ein Problem mit dem Erstellen eines symbolischen Haltepunkts bei UICollectionViewFlowLayoutBreakForInvalidSizes, um dies im Debugger abzufangen. Das Verhalten des UICollectionViewFlowLayout ist nicht definiert, weil: Die Elementbreite muss kleiner sein als die Breite des UICollectionView abzüglich der linken und rechten Werte der Abschnittseinfügungen abzüglich der linken und rechten Werte der Inhaltseinfügungen
EI Captain v2.0
1
@Jack wie folgt in viewDidLoad () verwenden. collectionViewName.collectionViewLayout = CenterAlignedCollectionViewFlowLayout ()
Mitesh Dobareeya
57

Die Top-Lösungen hier haben bei mir nicht sofort funktioniert, daher habe ich mir diese ausgedacht, die für jede horizontale Bildlaufsammlungsansicht mit Ablauflayout und nur einem Abschnitt funktionieren sollte:

- (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
quelle
Sie sollten cellSpacing mit 'cellCount - 1' und nicht mit cellCount multiplizieren. Dies wird deutlicher, wenn nur eine Zelle vorhanden ist: Zwischen den Zellen ist kein Platz.
Fábio Oliveira
Außerdem sollte ein MinimumInset vorhanden sein, das im 'inset = MAX (inset, 0.0)' verwendet werden kann. Linie. Es würde aussehen wie 'inset = MAX (inset, minimalInset);'. Sie sollten das gleiche MinimumInset auch auf der rechten Seite anwenden. Sammlungsansichten sehen so viel besser aus, wenn sie sich bis zum Rand erstrecken :)
Fábio Oliveira
Sowohl diese Lösung als auch die von @Sion funktionieren gleich gut, wenn die Zellengröße konstant ist. Würde jemand wissen, wie man die Größe der Zellen erhält? cellForItemAtIndexPath und visibleCells scheinen nur dann Informationen zurückzugeben, wenn die collectionView sichtbar ist - und zu diesem Zeitpunkt im Lebenszyklus ist dies nicht der Fall.
Dan Loughney
@Siegfoult Ich habe dies implementiert und es hat gut funktioniert, bis ich versucht habe, nach links zu scrollen, es kommt zurück in die Mitte.
Anirudha Mahale
34

Es ist einfach, Einfügungen dynamisch zu berechnen. Dieser Code zentriert immer Ihre Zellen:

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
quelle
4
Was war deine Modifikation?
Siriss
4
@ Siriss anstelle von SMEPGiPadViewControllerCellWidth können Sie collectionViewLayout.itemSize.width verwenden, und dies war wahrscheinlich seine Änderung
Michal Zaborowski
21

Ähnlich wie bei anderen Antworten ist dies eine dynamische Antwort, die für Zellen mit statischer Größe funktionieren sollte. Die einzige Änderung, die ich vorgenommen habe, ist, dass ich die Polsterung auf beiden Seiten anbringe. Wenn ich das nicht tat, hatte ich Probleme.


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

Schnell

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

Wenn weiterhin Probleme auftreten, stellen Sie sicher, dass Sie sowohl den minimumInteritemSpacingals minimumLineSpacingauch den gleichen Wert festlegen, da diese Werte miteinander in Beziehung zu stehen scheinen.

UICollectionViewFlowLayout *flowLayout = (UICollectionViewFlowLayout *)self.collectionView.collectionViewLayout;
flowLayout.minimumInteritemSpacing = 20.0;
flowLayout.minimumLineSpacing = 20.0;
kgaidis
quelle
Stürzt nur für mich ab
Supertecnoboff
Im ObjC-Objekt wird collectionViewLayout direkt verwendet. Es muss in UICollectionViewFlowLayout umgewandelt werden, um auf itemSize und MinimumInteritemSpacing zugreifen zu können
buttcmd
19

Fügen Sie dies in Ihren Delegierten für die Sammlungsansicht ein. Es berücksichtigt mehr der grundlegenden Einstellungen für das Ablauflayout als die anderen Antworten und ist daher allgemeiner.

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

schnelle Version (danke 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
quelle
3
Eigentlich in Ihrem UICollectionViewDelegateFlowLayout. Vielen Dank, dies funktioniert für eine Reihe von Zellen, die entweder eine Zeile oder mehrere Zeilen sind. Einige der anderen Antworten tun dies nicht.
Gene De Lisa
2
Konvertiert dies in Swift und es funktioniert großartig! Schnelle Version hier verfügbar .
g0ld2k
1
Das ist nicht ganz richtig, totalCellWidthsollte sein cellWidth*cellCount + spacing*(cellCount-1).
James P.
1
Nachdem ich mehrere dieser "Lösungen" ausprobiert hatte, war dies die einzige, die ich richtig arbeiten konnte, nachdem ich sie ein wenig für swift3.0
kemicofa ghost
@rugdealer Möchten Sie die Antwort ändern, um Ihre swift3-Änderungen widerzuspiegeln?
Bert
11

Meine Lösung für statisch große Sammlungsansichtszellen, die links und rechts aufgefüllt werden müssen.

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

Ich habe eine Tag-Leiste in meiner App, die ein UICollectionView& verwendet UICollectionViewFlowLayout, wobei eine Reihe von Zellen mittig ausgerichtet ist.

Um den richtigen Einzug zu erhalten, subtrahieren Sie die Gesamtbreite aller Zellen (einschließlich des Abstands) von der Breite Ihrer UICollectionViewund dividieren durch zwei.

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

=

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

Das Problem ist diese Funktion -

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

wird vorher aufgerufen ...

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

... Sie können also nicht über Ihre Zellen iterieren, um die Gesamtbreite zu bestimmen.

Stattdessen müssen Sie die Breite jeder Zelle erneut berechnen. In meinem Fall verwende ich sie, [NSString sizeWithFont: ... ]da meine Zellenbreiten vom UILabel selbst bestimmt werden.

- (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
quelle
Wie erhalten Sie interSpacing?
Drux
interSpacingist nur eine Konstante in meinem Code, die den Raum bestimmt, den ich zwischen UICollectionViewCells
Siôn
1
Dieser Raum kann jedoch variieren: FWIK Sie können nur den Mindestwert annehmen / angeben.
Drux
8

In Swift werden die Zellen im Folgenden gleichmäßig verteilt, indem die Seiten jeder Zelle mit der richtigen Polsterung versehen werden. Natürlich müssen Sie zuerst Ihre Zellenbreite kennen / einstellen.

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
quelle
Um den Zeilenabstand hinzuzufügen, ändern Sie 0 anstelle von 60,0
Oscar Castellon
8

Swift 2.0 funktioniert gut für mich!

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

Wobei: screenWight : im Grunde genommen die Breite meiner Sammlung (volle Bildschirmbreite) - Ich habe Konstanten erstellt: let screenWight: CGFloat = UIScreen.mainScreen (). Bounds.width, da self.view.bounds jedes Mal 600 - coz SizeClasses- Elemente anzeigt - Array von Zellen 50 - meine manuelle Zellenbreite 10 - mein Abstand zwischen Zellen

Dmitrii Z.
quelle
1
Sie können collectionView.frame.size.width anstelle von screenWight verwenden, nur für den Fall, dass Sie die Größe von UICollectionView
Oscar
2

Sie sind sich nicht sicher, ob dies in Xcode 5 neu ist oder nicht, aber Sie können jetzt den Größeninspektor über Interface Builder öffnen und dort einen Einschub festlegen. Dies verhindert, dass Sie benutzerdefinierten Code schreiben müssen, um dies für Sie zu tun, und sollte die Geschwindigkeit, mit der Sie die richtigen Offsets finden, drastisch erhöhen.

Sandy Chapman
quelle
1

Eine andere Möglichkeit, dies zu tun, besteht darin collectionView.center.x, Folgendes einzustellen:

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

In diesem Fall nur den richtigen Einschub respektieren, der für mich funktioniert.

Paul Peelen
quelle
1

Basierend auf der Antwort von user3676011 kann ich eine detailliertere mit kleiner Korrektur vorschlagen. Diese Lösung funktioniert hervorragend mit 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)
}

Es gab ein Problem in

(CGFloat (elements.count) * 10))

wo sollte zusätzlich -1erwähnt werden.

Novamax
quelle
1

Auf diese Weise erhielt ich eine Sammlungsansicht, deren Mitte mit einem festen Zwischenabstand ausgerichtet war

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

Arbeitsversion der Antwort auf Ziel C von kgaidis mit 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
quelle
1

Hier ist meine Lösung mit einigen Annahmen:

  • Es gibt nur einen Abschnitt
  • linker und rechter Einschub sind gleich
  • Zellenhöhe ist gleich

Sie können sich jederzeit an Ihre Bedürfnisse anpassen.

Zentriertes Layout mit variabler Zellenbreite:

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

Ergebnis:

Geben Sie hier die Bildbeschreibung ein

Whitney Foster
quelle
0

Hier ist, wie Sie es tun können und es funktioniert gut

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
quelle