Bagaimana cara mensejajarkan sel-sel UICollectionView?

99

Saat ini saya menggunakan UICollectionViewuntuk kisi antarmuka pengguna, dan berfungsi dengan baik. Namun, saya ingin mengaktifkan pengguliran horizontal. Grid mendukung 8 item per halaman dan ketika jumlah total item, katakanlah 4, beginilah cara item diatur dengan arah scroll horizontal diaktifkan:

0 0 x x
0 0 x x

Di sini 0 -> Item Koleksi dan x -> Sel Kosong

Apakah ada cara untuk membuatnya rata tengah seperti:

x 0 0 x
x 0 0 x

Agar konten terlihat lebih bersih?

Juga pengaturan di bawah ini mungkin juga menjadi solusi yang saya harapkan.

0 0 0 0
x x x x
RVN
sumber

Jawaban:

80

Saya pikir Anda dapat mencapai tampilan garis tunggal dengan menerapkan sesuatu seperti ini:

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

Anda harus bermain-main dengan nomor itu untuk mencari tahu bagaimana memaksa konten menjadi satu baris. 0 pertama, adalah argumen tepi atas, Anda juga dapat menyesuaikannya, jika Anda ingin memusatkan konten secara vertikal di layar.

rdelmar.dll
sumber
1
Ini adalah solusi yang bagus, terima kasih, namun ini memerlukan beberapa perhitungan ekstra di pihak saya.
RVN
Bagaimana Anda menghitung nilai-nilai ini?
jjxtra
7
UNTUK menghitung insets secara dinamis: CGFloat leftInset = (collectionView.frame.size.width-40 * [collectionView numberOfItemsInSection: section]) / 2; 40 = ukuran sel
Abduliam Rehmanius
1
@AbduliamRehmanius Tapi ini mengasumsikan bahwa sel tidak berjarak horizontal. Biasanya memang begitu. Dan tampaknya tidak mudah untuk menentukan ruang sebenarnya karena minimumInteritemSpacinghanya menunjukkan minimumnya.
Drux
5
Seharusnya begitu return UIEdgeInsetsMake(0, 100, 0, 100);. Jika ada ruang ekstra, tampilan koleksi akan memperluas jarak sementara, jadi Anda perlu memastikan LeftInset + CellWidth * Ncells + CellSpacing * (Ncells-1) + RightInset = CollectionViewWidth
vish
83

Bagi mereka yang mencari solusi untuk sel-sel collectionview selaras tengah dan lebar dinamis , seperti saya, saya akhirnya memodifikasi jawaban Angel untuk versi rata kiri untuk membuat subkelas tengah rata 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
sumber
3
Ini memberikan peringatan berikut, meskipun ini mungkin terjadi karena tata letak aliran subclass xxxx.CustomCollectionViewFlowLayout mengubah atribut yang dikembalikan oleh UICollectionViewFlowLayout tanpa menyalinnya Untuk memperbaiki, saya menggunakan kode dari [ 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
Terima kasih telah memposting. Menghabiskan beberapa hari mencoba untuk mencapai ini!
Mei Yang
Terima kasih atas pekerjaannya dan telah membagikan hasilnya dengan komunitas. Saya memperbarui kode. Pertama-tama, saya menambahkan perubahan yang direkomendasikan oleh @Ram. Kedua, saya ubah interItemSpacinguntuk menggunakan default minimumInteritemSpacing.
kelin
@Ram Saya memiliki masalah Membuat breakpoint simbolis di UICollectionViewFlowLayoutBreakForInvalidSizes untuk menangkap ini di debugger. Perilaku UICollectionViewFlowLayout tidak ditentukan karena: lebar item harus kurang dari lebar UICollectionView dikurangi bagian sisipan nilai kiri dan kanan, dikurangi sisipan konten nilai kiri dan kanan
EI Captain v2.0
1
@Jack gunakan seperti ini di viewDidLoad (). collectionViewName.collectionViewLayout = CenterAlignedCollectionViewFlowLayout ()
Mitesh Dobareeya
57

Solusi teratas di sini tidak bekerja untuk saya di luar kotak, jadi saya menemukan ini yang seharusnya berfungsi untuk tampilan koleksi pengguliran horizontal dengan tata letak aliran dan hanya satu bagian:

- (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
sumber
Anda harus mengalikan cellSpacing dengan 'cellCount - 1' dan bukan cellCount. Ini menjadi lebih jelas dalam kasus ketika hanya ada satu sel: tidak ada spasi antar sel.
Fábio Oliveira
Dan juga harus ada minimumInset untuk digunakan di 'inset = MAX (inset, 0.0);' baris. Ini akan terlihat seperti 'inset = MAX (inset, minimumInset);'. Anda harus menerapkan minimumInset yang sama di sisi kanan juga. Tampilan koleksi terlihat jauh lebih baik saat diperluas hingga ke tepi :)
Fábio Oliveira
Baik solusi ini dan yang dari @Sion bekerja sama dengan baik jika ukuran selnya konstan. Adakah yang tahu cara mendapatkan ukuran sel? cellForItemAtIndexPath dan visibleCells hanya tampak mengembalikan info ketika collectionView terlihat - dan pada titik ini dalam siklus hidup, tidak.
Dan Loughney
@Siegfoult Saya menerapkan ini dan berfungsi dengan baik sampai saya mencoba menggulir ke kiri, itu kembali ke tengah.
Anirudha Mahale
34

Mudah untuk menghitung insets secara dinamis, kode ini akan selalu memusatkan sel Anda:

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
sumber
4
Apa modifikasi Anda?
Siriss
4
@Siriss alih-alih SMEPGiPadViewControllerCellWidth Anda dapat menggunakan collectionViewLayout.itemSize.width, dan ini mungkin adalah modifikasinya
Michal Zaborowski
21

Mirip dengan jawaban lain, ini adalah jawaban dinamis yang seharusnya berfungsi untuk sel berukuran statis. Modifikasi yang saya buat adalah saya meletakkan bantalan di kedua sisi. Jika saya tidak melakukan ini, saya mengalami masalah.


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

Cepat

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

Selain itu, jika Anda masih mengalami masalah, pastikan Anda menyetel minimumInteritemSpacingdan minimumLineSpacingke nilai yang sama karena nilai ini tampaknya saling terkait.

UICollectionViewFlowLayout *flowLayout = (UICollectionViewFlowLayout *)self.collectionView.collectionViewLayout;
flowLayout.minimumInteritemSpacing = 20.0;
flowLayout.minimumLineSpacing = 20.0;
kgaidis
sumber
Hanya crash untuk saya
Supertecnoboff
Dalam objek ObjC collectionViewLayout digunakan secara langsung. Ini harus dicor ke UICollectionViewFlowLayout untuk dapat mengakses itemSize dan minimumInteritemSpacing
buttcmd
19

Masukkan ini ke dalam delegasi tampilan koleksi Anda. Ini mempertimbangkan lebih banyak pengaturan tata letak aliran dasar daripada jawaban lain dan dengan demikian lebih umum.

- (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 cepat (terima kasih 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
sumber
3
Sebenarnya di UICollectionViewDelegateFlowLayout Anda. Terima kasih, ini berfungsi untuk sejumlah sel yang terdiri dari satu baris atau beberapa baris. Beberapa jawaban lain tidak.
Gene De Lisa
2
Mengubah ini menjadi Swift dan berfungsi dengan baik! Versi Swift tersedia di sini .
g0ld2k
1
Ini tidak sepenuhnya benar, totalCellWidthseharusnya cellWidth*cellCount + spacing*(cellCount-1).
James P
1
Setelah mencoba beberapa "solusi" ini, ini adalah satu-satunya solusi yang dapat saya lakukan dengan benar setelah memodifikasinya sedikit untuk swift3.0
kemicofa ghost
@rugdealer Apakah Anda ingin mengubah jawaban untuk mencerminkan perubahan swift3 Anda?
bert
11

Solusi saya untuk sel tampilan koleksi berukuran statis yang perlu memiliki bantalan di kiri dan kanan-

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

Saya memiliki bilah tag di aplikasi saya yang menggunakan UICollectionView& UICollectionViewFlowLayout, dengan satu baris sel rata tengah.

Untuk mendapatkan indentasi yang benar, Anda mengurangi total lebar semua sel (termasuk spasi) dari lebar Anda UICollectionView, dan membaginya dengan dua.

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

=

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

Masalahnya adalah fungsi ini -

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

dipanggil sebelumnya ...

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

... jadi Anda tidak bisa mengulang sel Anda untuk menentukan lebar total.

Sebagai gantinya Anda perlu menghitung lebar setiap sel lagi, dalam kasus saya, saya gunakan [NSString sizeWithFont: ... ]karena lebar sel saya ditentukan oleh UILabel itu sendiri.

- (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
sumber
Bagaimana Anda mendapatkannya interSpacing?
Drux
interSpacinghanyalah konstanta dalam kode saya, yang menentukan ruang yang saya miliki antara UICollectionViewCells
Siôn
1
Tetapi spasi ini dapat bervariasi: FWIK Anda hanya dapat mengasumsikan / menyatakan nilai minimum.
Drux
8

Di Swift, berikut ini akan mendistribusikan sel secara merata dengan menerapkan jumlah padding yang benar di sisi setiap sel. Tentunya Anda perlu mengetahui / mengatur lebar sel Anda terlebih dahulu.

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
sumber
Untuk menambahkan spasi baris, ubah 0, bukan 60.0
oscar castellon
8

Swift 2.0 Bekerja dengan baik untuk saya!

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

Di mana: screenWight : pada dasarnya adalah lebar koleksi saya (lebar layar penuh) - Saya membuat konstanta: biarkan screenWight: CGFloat = UIScreen.mainScreen (). Bounds.width karena self.view.bounds menunjukkan setiap waktu 600 - karena elemen SizeClasses - array sel 50 - lebar sel manual saya 10 - jarak antar sel saya

Dmitrii Z
sumber
1
Anda dapat menggunakan collectionView.frame.size.width sebagai ganti screenWight, jika Anda ingin mengubah ukuran UICollectionView
Oscar
2

Tidak yakin apakah ini baru di Xcode 5 atau tidak, tetapi Anda sekarang dapat membuka Size Inspector melalui Interface Builder dan mengatur inset di sana. Itu akan mencegah Anda dari keharusan menulis kode khusus untuk melakukan ini untuk Anda dan harus secara drastis meningkatkan kecepatan di mana Anda menemukan offset yang tepat.

Sandy Chapman
sumber
1

Cara lain untuk melakukan ini adalah menyetel collectionView.center.x, seperti:

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

Dalam hal ini, hanya menghormati inset kanan yang berhasil untuk saya.

Paul Peelen
sumber
1

Berdasarkan jawaban pengguna3676011, saya dapat menyarankan yang lebih detail dengan sedikit koreksi. Solusi ini berfungsi dengan baik di 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)
}

Ada masalah di

(CGFloat (element.count) * 10))

di mana harus -1disebutkan lebih lanjut.

Novamax
sumber
1

Ini adalah bagaimana saya memperoleh tampilan koleksi yang sejajar tengah dengan spasi Interitem tetap

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

Versi kerja jawaban Objective C kgaidis menggunakan 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
sumber
1

Inilah solusi saya dengan beberapa asumsi:

  • hanya ada satu bagian
  • insets kiri dan kanan sama
  • tinggi sel sama

Jangan ragu untuk menyesuaikan untuk memenuhi kebutuhan Anda.

Tata letak tengah dengan lebar sel variabel:

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

Hasil:

masukkan deskripsi gambar di sini

Whitney Foster
sumber
0

Inilah cara Anda melakukannya dan berfungsi dengan baik

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
sumber