create custom collection view layout in swift

To create a custom UICollectionView layout in Swift, you must first create a class that subclasses UICollectionViewLayout. This class will be responsible for calculating the position of all the cells and supplementary views in the collection view.

Here's an example of what the implementation might look like for a collection view layout that displays cells in a grid:

main.swift
class GridCollectionViewLayout: UICollectionViewLayout {
    
    let numberOfColumns = 2
    let cellPadding: CGFloat = 6
    
    var cache: [UICollectionViewLayoutAttributes] = []
    
    override func prepare() {
        guard cache.isEmpty, let collectionView = collectionView else { return }
        
        let columnWidth = (collectionView.bounds.width - cellPadding * CGFloat(numberOfColumns + 1)) / CGFloat(numberOfColumns)
        var xOffset: [CGFloat] = []
        for column in 0..<numberOfColumns {
            xOffset.append(cellPadding + CGFloat(column) * (columnWidth + cellPadding))
        }
        var yOffset: [CGFloat] = .init(repeating: 0, count: numberOfColumns)
        
        for item in 0..<collectionView.numberOfItems(inSection: 0) {
            let indexPath = IndexPath(item: item, section: 0)
            
            let column = item % numberOfColumns
            let x = xOffset[column]
            let y = yOffset[column]
            let height = CGFloat(Int.random(in: 150...250))
            let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
            attributes.frame = CGRect(x: x, y: y, width: columnWidth, height: height)
            cache.append(attributes)
            
            yOffset[column] += height + cellPadding
        }
    }
    
    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        return cache.filter { $0.frame.intersects(rect) }
    }
    
    override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        return cache.first { $0.indexPath == indexPath }
    }
    
    override var collectionViewContentSize: CGSize {
        guard let lastAttribute = cache.last else { return .zero }
        return CGSize(width: collectionView?.bounds.width ?? 0, height: lastAttribute.frame.maxY + cellPadding)
    }
}
1906 chars
46 lines

In the prepare() method, you first calculate the width of each column and the initial x and y offsets, and then loop through each item in the collection view. For each item, you calculate which column it belongs to based on its index, and then set its frame based on the column width, the current x and y offset, and a random height.

In the layoutAttributesForElements(in:) method, you return an array of UICollectionViewLayoutAttributes objects that intersect with the passed-in rect. In the layoutAttributesForItem(at:) method, you return the attributes for a specific item.

Finally, in the collectionViewContentSize property override, you calculate the height of the content based on the position of the last item's frame and the cell padding. This determines the height of the scrollable area of the collection view.

gistlibby LogSnag