Search code examples
iosswiftuicollectionviewuikituicollectionviewflowlayout

UICollectionView with fixed number of columns and dynamic cell height


A lot of good info about UICollectionView is to be found, but I was unable to answer this:

How would you go about creating a vertically scrolling UICollectionView with a fixed number of columns while the cell has dynamic height.

My plan was to use self sizing cells but since I want a fixed number of columns that would require each cell to have a width constraint that is calculated from the collectionview width, this seems quirky and might give issues when changing device orientation. To be specific I want the collection to have on single column in compact and two in regular.

I also tried implementing collectionView(UICollectionView, layout: UICollectionViewLayout, sizeForItemAt: IndexPath) -> CGSize on UICollectionViewDelegateFlowLayout this way it's easy to set the width of the cells so it fits the number of columns I want, but it seems it's not possible to combine this with self sizing cell height.


Solution

  • Create a helper class with following enum.

    enum DeviceTraitStatus {
        ///IPAD and others: Width: Regular, Height: Regular
        case wRhR
        ///Any IPHONE Portrait Width: Compact, Height: Regular
        case wChR
        ///IPHONE Plus/Max Landscape Width: Regular, Height: Compact
        case wRhC
        ///IPHONE landscape Width: Compact, Height: Compact
        case wChC
    
        static var current:DeviceTraitStatus{
    
            switch (UIScreen.main.traitCollection.horizontalSizeClass, UIScreen.main.traitCollection.verticalSizeClass){
    
            case (UIUserInterfaceSizeClass.regular, UIUserInterfaceSizeClass.regular):      
                return .wRhR
            case (UIUserInterfaceSizeClass.compact, UIUserInterfaceSizeClass.regular):
                return .wChR
            case (UIUserInterfaceSizeClass.regular, UIUserInterfaceSizeClass.compact):
                return .wRhC
            case (UIUserInterfaceSizeClass.compact, UIUserInterfaceSizeClass.compact):
                return .wChC
            default:
                return .wChR
    
            }
    
        }
    
    }
    

    Then in the collection view delegate method calculate the width accordingly.

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize
    {
        return size(for: indexPath)
    }
    
    private func size(for indexPath: IndexPath) -> CGSize
    {
        let cell = Bundle.main.loadNibNamed("YourCollectionViewCell", owner: self, options: nil)?.first as! BoxSelectCollectionCell
    
        cell.setNeedsLayout()
        cell.layoutIfNeeded()
    
        // Call the method to calculate the width
        var width: CGFloat = 0
        switch DeviceTraitStatus.current
       {
              case .wChR, .wChC: width = (collectionView.frame.size.width - padding/2).rounded(.down)
    
              case .wRhR, .wRhC: width = (collectionView.frame.size.width/2 - padding/2).rounded(.down)
    
       }
        let height: CGFloat = 0
    
        let targetSize = CGSize(width: width, height: height)
    
        var size = cell.contentView.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: .defaultHigh, verticalFittingPriority: .fittingSizeLevel)
    
        return size
    }