Ask Your Question

Revision history [back]

click to hide/show revision 1
initial version

A possible approach to implement a UICollectionViewController flow layout that limits the content width for optimal readability is to subclass UICollectionViewFlowLayout and override the computeItemFrames method to adjust the layout attributes of the items based on a maximum width value:

class MaxWidthFlowLayout: UICollectionViewFlowLayout {

    let maxWidth: CGFloat = 400 // set your preferred max width here

    override func prepare() {
        super.prepare()
        scrollDirection = .vertical
        minimumInteritemSpacing = 16
        minimumLineSpacing = 16
    }

    override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
        return true // enable dynamic resizing
    }

    override func computeItemFrames() {
        guard let collectionView = collectionView else {
            return
        }
        let contentWidth = collectionView.bounds.width - collectionView.safeAreaInsets.horizontalCombined
        let columns = max(Int(contentWidth / (maxWidth + minimumInteritemSpacing)), 1)
        let itemWidth = (contentWidth - CGFloat(columns - 1) * minimumInteritemSpacing) / CGFloat(columns)
        let xOffsets = (0..<columns).map { CGFloat($0) * (itemWidth + minimumInteritemSpacing) }
        var yOffsets = [CGFloat](repeating: 0, count: columns)
        var itemFrames = [CGRect]()
        for itemIndex in 0..<collectionView.numberOfItems(inSection: 0) {
            let indexPath = IndexPath(item: itemIndex, section: 0)
            let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
            let columnIndex = yOffsets.firstIndex(of: yOffsets.min()!) ?? 0
            let x = xOffsets[columnIndex]
            let y = yOffsets[columnIndex]
            let height = // calculate the desired item height based on the content
            let frame = CGRect(x: x, y: y, width: itemWidth, height: height)
            attributes.frame = frame
            itemFrames.append(frame)
            yOffsets[columnIndex] += height + minimumLineSpacing
        }
        collectionViewContentSize = CGSize(width: contentWidth, height: yOffsets.max() ?? 0)
        cachedItemFrames = itemFrames
    }

}

Then, you can set the flow layout of your UICollectionViewController to an instance of MaxWidthFlowLayout:

let flowLayout = MaxWidthFlowLayout()
collectionView.collectionViewLayout = flowLayout

This custom layout should arrange the items in vertical columns that adjust their width and height to fit the available content width, limiting the line length and improving the readability of the text or other elements inside the items. When the collection view bounds change, the layout invalidates itself and recomputes the item frames, adapting to the new size. You can customize the configuration properties of the flow layout and the item height calculation to suit your specific use case.