UICollectionView Dynamic Cell Height as per the content | Pinterest Layouts

3.9k Views Asked by At

I am trying to do a layout as Pinterest uses for their image gallery. But the issue is that I'm returning the image height to the size of the cells and when the images have different heights, it leaves spaces in the cells.

I have walked through lots of articles, but I have not found a simple solution. Is there any simple way we can do this?

Please see this image

class ViewController: UIViewController {

    @IBOutlet var collectionVIew: UICollectionView!
    var imgData = [#imageLiteral(resourceName: "p1"),#imageLiteral(resourceName: "p2"),#imageLiteral(resourceName: "p3"),#imageLiteral(resourceName: "p4"),#imageLiteral(resourceName: "p5"),#imageLiteral(resourceName: "p6"),#imageLiteral(resourceName: "p7"),#imageLiteral(resourceName: "p8"),#imageLiteral(resourceName: "p1"),#imageLiteral(resourceName: "p2"),#imageLiteral(resourceName: "p3"),#imageLiteral(resourceName: "p4"),#imageLiteral(resourceName: "p5"),#imageLiteral(resourceName: "p6"),#imageLiteral(resourceName: "p7"),#imageLiteral(resourceName: "p8"),#imageLiteral(resourceName: "p1"),#imageLiteral(resourceName: "p2"),#imageLiteral(resourceName: "p3"),#imageLiteral(resourceName: "p4"),#imageLiteral(resourceName: "p5"),#imageLiteral(resourceName: "p6"),#imageLiteral(resourceName: "p7"),#imageLiteral(resourceName: "p8"),#imageLiteral(resourceName: "p1"),#imageLiteral(resourceName: "p2"),#imageLiteral(resourceName: "p3"),#imageLiteral(resourceName: "p4"),#imageLiteral(resourceName: "p5"),#imageLiteral(resourceName: "p6"),#imageLiteral(resourceName: "p7"),#imageLiteral(resourceName: "p8")]

    override func viewDidLoad() {
        super.viewDidLoad()
    }
}

extension ViewController: UICollectionViewDelegateFlowLayout {
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        let width = collectionView.frame.size.width
        let img = imgData[indexPath.item]
        // 335 is the width of my cell image
        return CGSize(width: width/2-15, height: (img.size.height / 335) * width)
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
        return 10
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
        return 5
    }
}

extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource {

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return imgData.count
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CollectionViewCell
        cell.imgView.image = imgData[indexPath.row];
        cell.imgView.contentMode = .scaleAspectFit
        return cell
    }
}
2

There are 2 best solutions below

3
On BEST ANSWER

What you have mentioned can be achieved using flow layout — a layout class provided by UIKit.

This is what you are looking for:

https://www.raywenderlich.com/392-uicollectionview-custom-layout-tutorial-pinterest

I hope you can put some effort into reading this very simple post and follow each step carefully.

8
On

You are trying to fit non-square images into a square hole. When we do this something has to be given. In this case, I would recommend you make each of your cells a fixed height and set an aspect fill content mode on them so they fill the cell's frame completely. The downside is that some of the larger images will have part of them cut off. The other option is to use the aspect fit content mode which will scale the image to fit without losing a part of the image. This would leave you with the cell spacing again.

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    let width = collectionView.frame.size.width
    return CGSize(width: width/2-15, height: 100) // fixed height
}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CollectionViewCell
    cell.imgView.image = imgData[indexPath.row];
    cell.imgView.contentMode = .scaleAspectFill // Set the scale mode to aspect fill
    return cell
}

Alternatively, you can check out some open source libraries that might implement this for you. This one looks promising https://github.com/abdullahselek/ASCollectionView

You can check out a larger list of collection view open-source libraries here https://github.com/vsouza/awesome-ios#collection-view

If you want the cells to have different heights you need to calculate the correct height based on the aspect ratio of the image and the width that is fixed. You calculate the aspect ratio in this case by the height/width when our width is fixed and our height is dynamic. Then multiply that by the width of the cell of the image to get our dynamic cell height.

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
    let width = collectionView.frame.size.width / 2 - 15;
    let img = imgData[indexPath.item]
    return CGSize(width: width, height: (img.size.height / img.size.width) * width)
}

You should set your content mode to aspect fit in this case.

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CollectionViewCell
    cell.imgView.image = imgData[indexPath.row];
    cell.imgView.contentMode = .scaleAspectFit // Set the scale mode to aspect fit
    return cell
}