Search code examples
iosswiftuicollectionviewcgsize

Faster way to estimate cell height in a CollectionView


I have an infinite scroll in my UICollectionView and I've noticed that the way I estimate my cell height is the bottleneck of my collection view. It causes some long delays the more I scroll my collection view.

Is there a better way to estimate the heights of my cells?

The cells have different heights because I have a UILabel in each of them. I assign a NSMutableAttributedStrings of different lengths to those UILabels:

let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .justified
paragraphStyle.lineSpacing = 5.0

let attributedText = NSMutableAttributedString(string: "   \(post.caption)", attributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 15), .paragraphStyle: paragraphStyle, .baselineOffset: NSNumber(value: 0)])

attributedText.append(NSAttributedString(string: "\n\n", attributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 4)]))

let timeAgoDisplay = post.creationDate.timeAgoDisplay()
attributedText.append(NSAttributedString(string: timeAgoDisplay, attributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 14), NSAttributedStringKey.foregroundColor: UIColor.storiesLightGray()]))

captionLabel.attributedText = attributedText

My sizeForItemAt method. I've noticed that calling dummyCell.layoutIfNeeded() makes my app very slow once I have more than 200 items in my collection view:

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {

        var height: CGFloat = 180

        let frame = CGRect(x: 0, y: 0, width: view.frame.width, height: height)
        let dummyCell = HomePostCell(frame: frame)

        dummyCell.post = presenter.posts[indexPath.item]
        dummyCell.layoutIfNeeded()

        let targetSize = CGSize(width: view.frame.width, height: 5000)
        let estimatedSize = dummyCell.systemLayoutSizeFitting(targetSize)

        let newHeight = max(height, estimatedSize.height)

        return CGSize(width: view.frame.width, height: newHeight)
 }

Thanks!


Solution

  • Ideally you should not use systemLayoutSizeFitting in your sizeForItemAt, because, as you say, it is slow.

    You could precache some cell data, calculate the sizes, and store them in an array or similar, so that sizeForItemAt merely has to do the lookup in the array — which is fast.

    And you don't really need to use systemLayoutSizeFitting, either; you can use your knowledge of the label size and contents to calculate the size (e.g. using NSAttributedString measurement methods).

    That is what we used to do before systemLayoutSizeFitting or auto layout existed, and it remains far faster.