How to center align the cells of a UICollectionView?

前端 未结 18 2462
梦如初夏
梦如初夏 2020-11-28 01:33

I am currently using UICollectionView for the user interface grid, and it works fine. However, I\'d like to be enable horizontal scrolling. The grid supports 8

相关标签:
18条回答
  • 2020-11-28 01:50

    I have a tag bar in my app which uses an UICollectionView & UICollectionViewFlowLayout, with one row of cells center aligned.

    To get the correct indent, you subtract the total width of all the cells (including spacing) from the width of your UICollectionView, and divide by two.

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

    The problem is this function -

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

    is called before...

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

    ...so you can't iterate over your cells to determine the total width.

    Instead you need to calculate the width of each cell again, in my case I use [NSString sizeWithFont: ... ] as my cell widths are determined by the UILabel itself.

    - (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);
    }
    
    0 讨论(0)
  • 2020-11-28 01:52

    For those looking for a solution to center-aligned, dynamic-width collectionview cells, as I was, I ended up modifying Angel's answer for a left-aligned version to create a center-aligned subclass for 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
        }
    }
    

    0 讨论(0)
  • 2020-11-28 01:54

    Another way of doing this is setting the collectionView.center.x, like so:

    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 this case, only respecting the right inset which works for me.

    0 讨论(0)
  • 2020-11-28 01:56

    The top solutions here did not work for me out-of-the-box, so I came up with this which should work for any horizontal scrolling collection view with flow layout and only one section:

    - (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);
        }
    
    0 讨论(0)
  • 2020-11-28 01:57

    Put this into your collection view delegate. It considers more of the the basic flow layout settings than the other answers and is thereby more generic.

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

    swift version (thanks 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
        }
    }
    
    0 讨论(0)
  • 2020-11-28 01:59

    Working version of kgaidis's Objective C answer using 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)
    }
    
    0 讨论(0)
提交回复
热议问题