Làm cách nào để căn giữa các ô của UICollectionView?


99

Tôi hiện đang sử dụng UICollectionViewcho lưới giao diện người dùng và nó hoạt động tốt. Tuy nhiên, tôi muốn bật tính năng cuộn ngang. Lưới hỗ trợ 8 mục trên mỗi trang và khi tổng số mục là 4, đây là cách sắp xếp các mục với hướng cuộn ngang được bật:

0 0 x x
0 0 x x

Tại đây 0 -> Mục Bộ sưu tập và x -> Ô trống

Có cách nào để làm cho chúng căn giữa như:

x 0 0 x
x 0 0 x

Để nội dung trông sạch sẽ hơn?

Ngoài ra cách sắp xếp dưới đây cũng có thể là một giải pháp mà tôi đang mong đợi.

0 0 0 0
x x x x

Câu trả lời:


80

Tôi nghĩ rằng bạn có thể đạt được giao diện dòng đơn bằng cách triển khai một cái gì đó như sau:

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

Bạn sẽ phải thử với con số đó để tìm ra cách buộc nội dung vào một dòng duy nhất. Số 0 đầu tiên, là đối số cạnh trên, bạn cũng có thể điều chỉnh đối số đó, nếu bạn muốn căn giữa nội dung theo chiều dọc của màn hình.


1
Đây là giải pháp tốt, cảm ơn, tuy nhiên điều này đòi hỏi một số tính toán thêm từ phía tôi.
RVN

Làm thế nào để bạn tính toán các giá trị này?
jjxtra

7
ĐỂ tính toán động các giá trị: CGFloat leftInset = (collectionView.frame.size.width-40 * [collectionView numberOfItemsInSection: section]) / 2; 40 = kích thước của tế bào
Abduliam Rehmanius

1
@AbduliamRehmanius Nhưng điều này giả định rằng các ô không có khoảng cách theo chiều ngang. Thông thường là như vậy. Và có vẻ như không đơn giản để xác định không gian thực tế vì minimumInteritemSpacingchỉ cho biết mức tối thiểu của chúng.
Drux

5
Nó thực sự nên được return UIEdgeInsetsMake(0, 100, 0, 100);. Nếu có thêm không gian, quan điểm bộ sưu tập sẽ mở rộng khoảng cách tạm thời, vì vậy bạn cần phải chắc chắn LeftInset + CellWidth * Ncells + cellspacing * (Ncells-1) + RightInset = CollectionViewWidth
Vish

82

Đối với những người đang tìm kiếm giải pháp cho các ô collectionview được căn giữa, có chiều rộng động , tôi đã kết thúc việc sửa đổi câu trả lời của Angel cho một phiên bản căn trái để tạo một lớp con căn giữa cho 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


3
Điều này đã đưa ra cảnh báo sau, mặc dù Điều này có thể xảy ra do lớp con của bố cục luồng xxxx.CustomCollectionViewFlowLayout đang sửa đổi các thuộc tính được trả về bởi UICollectionViewFlowLayout mà không sao chép chúng. Để sửa, tôi đã sử dụng mã từ [ 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

Cảm ơn đã đăng bài viết. Đã dành một vài ngày để cố gắng đạt được điều này!
Tháng

Cảm ơn bạn đã làm việc và chia sẻ kết quả với cộng đồng. Tôi đã cập nhật mã. Trước hết, tôi đã thêm các thay đổi do @Ram đề xuất. Thứ hai, tôi thay đổi interItemSpacingđể sử dụng mặc định minimumInteritemSpacing.
kelin

@Ram Tôi gặp sự cố Tạo điểm ngắt tượng trưng tại UICollectionViewFlowLayoutBreakForInvalidSizes để bắt lỗi này trong trình gỡ lỗi. Hành vi của UICollectionViewFlowLayout không được xác định vì: chiều rộng mục phải nhỏ hơn chiều rộng của UICollectionView trừ đi phần chèn giá trị bên trái và bên phải, trừ đi phần nội dung chèn giá trị bên trái và bên phải
EI Captain v2.0

1
@Jack sử dụng như thế này trong viewDidLoad (). collectionViewName.collectionViewLayout = CenterAlignedCollectionViewFlowLayout ()
Mitesh Dobareeya

57

Các giải pháp hàng đầu ở đây không phù hợp với tôi, vì vậy tôi đã nghĩ ra giải pháp này sẽ hoạt động cho bất kỳ chế độ xem bộ sưu tập cuộn ngang nào với bố cục luồng và chỉ một phần:

- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section
    {
    // Add inset to the collection view if there are not enough cells to fill the width.
    CGFloat cellSpacing = ((UICollectionViewFlowLayout *) collectionViewLayout).minimumLineSpacing;
    CGFloat cellWidth = ((UICollectionViewFlowLayout *) collectionViewLayout).itemSize.width;
    NSInteger cellCount = [collectionView numberOfItemsInSection:section];
    CGFloat inset = (collectionView.bounds.size.width - (cellCount * cellWidth) - ((cellCount - 1)*cellSpacing)) * 0.5;
    inset = MAX(inset, 0.0);
    return UIEdgeInsetsMake(0.0, inset, 0.0, 0.0);
    }

Bạn nên nhân cellSpacing với 'cellCount - 1' chứ không phải cellCount. Nó trở nên rõ ràng hơn trong trường hợp chỉ có một ô: không có khoảng trống giữa các ô.
Fábio Oliveira,

Và cũng phải có một Inset tối thiểu được sử dụng trong 'inset = MAX (inset, 0.0);' hàng. Nó sẽ giống như 'inset = MAX (inset, MinimumInset);'. Bạn cũng nên áp dụng cài đặt tối thiểu đó ở phía bên phải. Xem bộ sưu tập tìm kiếm tốt hơn rất nhiều khi họ mở rộng quyền cạnh :)
Fábio Oliveira

Cả giải pháp này và giải pháp từ @Sion đều hoạt động tốt như nhau nếu kích thước ô không đổi. Có ai biết cách lấy kích thước của các ô không? cellForItemAtIndexPath và AvailableCells dường như chỉ trả về thông tin khi collectionView hiển thị - và tại thời điểm này trong vòng đời, điều này không xảy ra.
Dan Loughney

@Siegfoult Tôi đã triển khai điều này và nó hoạt động tốt cho đến khi tôi cố gắng cuộn sang trái, nó sẽ trở lại giữa.
Anirudha Mahale

34

Thật dễ dàng để tính toán động nội bộ, mã này sẽ luôn căn giữa các ô của bạn:

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

4
Sửa đổi của bạn là gì?
Siriss

4
@Siriss thay vì SMEPGiPadViewControllerCellWidth bạn có thể sử dụng collectionViewLayout.itemSize.width, và điều này có lẽ anh ấy sửa đổi
Michal Zaborowski

21

Tương tự như các câu trả lời khác, đây là câu trả lời động sẽ hoạt động cho các ô có kích thước tĩnh. Một sửa đổi tôi đã thực hiện là tôi đặt đệm ở cả hai bên. Nếu tôi không làm điều này, tôi đã gặp vấn đề.


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

Nhanh

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

Ngoài ra, nếu bạn vẫn đang gặp vấn đề, hãy chắc chắn rằng bạn đặt cả minimumInteritemSpacingminimumLineSpacingcác giá trị tương tự từ những giá trị này dường như được với nhau.

UICollectionViewFlowLayout *flowLayout = (UICollectionViewFlowLayout *)self.collectionView.collectionViewLayout;
flowLayout.minimumInteritemSpacing = 20.0;
flowLayout.minimumLineSpacing = 20.0;

Chỉ cần treo đối với tôi
Supertecnoboff

Trong đối tượng objC collectionViewLayout được sử dụng trực tiếp. Nó phải được đúc để UICollectionViewFlowLayout để có thể truy cập vào itemSize và minimumInteritemSpacing
buttcmd

19

Đặt cái này vào đại biểu chế độ xem bộ sưu tập của bạn. Nó xem xét nhiều cài đặt bố cục luồng cơ bản hơn các câu trả lời khác và do đó chung chung hơn.

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

phiên bản nhanh (cảm ơn 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
    }
}

3
Trên thực tế trong UICollectionViewDelegateFlowLayout của bạn. Cảm ơn, điều này hoạt động đối với một số ô là một hàng hoặc nhiều hàng. Một số câu trả lời khác thì không.
Gene De Lisa,

2
Đã chuyển đổi nó thành Swift và nó hoạt động rất tốt! Phiên bản Swift có sẵn tại đây .
g0ld2k

1
Điều này không hoàn toàn chính xác, totalCellWidthnên được cellWidth*cellCount + spacing*(cellCount-1).
James P

1
Sau khi thử nhiều những "giải pháp" này là người duy nhất tôi đã có thể để có được để làm việc một cách chính xác sau khi sửa đổi nó một chút cho swift3.0
kemicofa ma

@rugdealer Bạn có muốn sửa đổi câu trả lời để phản ánh những thay đổi trong swift3 của mình không?
bert

11

Giải pháp của tôi cho các ô xem bộ sưu tập có kích thước tĩnh cần có phần đệm ở bên trái và bên phải-

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

9

Tôi có một thanh thẻ trong ứng dụng của mình sử dụng UICollectionView&UICollectionViewFlowLayout , với một hàng ô được căn giữa.

Để có được thụt lề chính xác, bạn lấy chiều rộng của ô trừ đi tổng chiều rộng của tất cả các ô (bao gồm cả khoảng cách) UICollectionViewvà chia cho hai.

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

=

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

Vấn đề là chức năng này -

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

được gọi trước ...

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

... vì vậy bạn không thể lặp lại các ô của mình để xác định tổng chiều rộng.

Thay vào đó, bạn cần phải tính toán lại chiều rộng của từng ô, trong trường hợp của tôi, tôi sử dụng [NSString sizeWithFont: ... ]vì chiều rộng ô của tôi được xác định bởi chính Nhãn UIL.

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

Làm thế nào để bạn có được interSpacing?
Drux

interSpacingchỉ là một hằng số trong mã của tôi, xác định không gian Tôi có giữa UICollectionViewCells
Sion

1
Nhưng không gian này có thể thay đổi: FWIK bạn chỉ có thể giả định / nêu giá trị tối thiểu.
Drux

8

Trong Swift, phần sau sẽ phân phối các ô đồng đều bằng cách áp dụng đúng số lượng đệm trên các cạnh của mỗi ô. Tất nhiên, bạn cần biết / đặt chiều rộng ô của mình trước.

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

Để thêm khoảng cách hàng, hãy thay đổi 0 thay vì 60.0
oscar castellon

8

Swift 2.0 phù hợp với tôi!

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

Trong đó: screenWight : về cơ bản là chiều rộng bộ sưu tập của tôi (chiều rộng toàn màn hình) - Tôi đã tạo hằng số: let screenWight: CGFloat = UIScreen.mainScreen (). Bounds.width bởi vì self.view.bounds luôn hiển thị 600 - coz các phần tử SizeClasses - mảng ô 50 - chiều rộng ô thủ công của tôi 10 - khoảng cách giữa các ô


1
Bạn có thể sử dụng collectionView.frame.size.width thay vì screenWight, đề phòng trường hợp bạn muốn thay đổi kích thước của UICollectionView
Oscar


2

Không chắc liệu điều này có mới trong Xcode 5 hay không, nhưng bây giờ bạn có thể mở Trình kiểm tra kích thước thông qua Trình tạo giao diện và đặt nội dung ở đó. Điều đó sẽ ngăn bạn phải viết mã tùy chỉnh để làm điều này cho bạn và sẽ tăng đáng kể tốc độ mà bạn tìm thấy các hiệu số thích hợp.


1

Một cách khác để làm điều này là thiết lập collectionView.center.x, như sau:

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

Trong trường hợp này, chỉ tôn trọng nội dung phù hợp phù hợp với tôi.


1

Dựa trên câu trả lời của user3676011, tôi có thể đề xuất một câu trả lời chi tiết hơn với một số chỉnh sửa nhỏ. Giải pháp này hoạt động tốt trên 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)
}

Đã xảy ra sự cố trong

(CGFloat (Elements.count) * 10))

nơi cần được -1đề cập bổ sung .


1

Đây là cách tôi có được chế độ xem bộ sưu tập được căn giữa với khoảng cách Interitem cố định

#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

1

Phiên bản làm việc của câu trả lời Objective C của kgaidis bằng 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)
}

1

Đây là giải pháp của tôi với một số giả định:

  • chỉ có một phần
  • nội tuyến trái và phải bằng nhau
  • chiều cao ô giống nhau

Hãy tự do điều chỉnh để đáp ứng nhu cầu của bạn.

Bố cục căn giữa với chiều rộng ô thay đổi:

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

Kết quả:

nhập mô tả hình ảnh ở đây


0

Đây là cách bạn có thể làm và nó hoạt động tốt

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)
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.