0

When adjusting a label's text for self seizing collectionView cells I use the following function in sizeForItem:

func estimatedLabelHeight(text: String, width: CGFloat, font: UIFont) -> CGFloat {
    let size = CGSize(width: width, height: 1000)
    let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
    let attributes = [NSAttributedStringKey.font: font]
    let rectangleHeight = String(text).boundingRect(with: size, options: options, attributes: attributes, context: nil).height
    return rectangleHeight
}

The function works fine and my cells expand accordingly.

What has happened though is there are several line breaks inside some of the text from the pricesLabel that get fed into the function. The line breaks are too "tight" so I followed this answer when creating my label to expand the spacing in between the line breaks.

let attributedString = NSMutableAttributedString(string: pricesText)
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = 7 // if I set this at 2 I have no problems
attributedString.addAttribute(.paragraphStyle, value: paragraphStyle, range: NSMakeRange(0, attributedString.length))

The problem is the text from pricesLabel inside the cell expands to far downwards. Using paragraphStyle.lineSpacing = 7 creates the space I want but it causes problems. If I set this to paragraphStyle.lineSpacing = 2 have no problems but the spacing is to tight.

enter image description here

As you can see in the picture the cell sizes the way it's supposed to but the the line break spacing in between the $8.00 and $12.00 makes the text expand to far and the text of $20.00 from the computedTotalLabel gets obscured.

I called sizeToFit() in layoutSubViews() but it made no difference:

override func layoutSubviews() {
    super.layoutSubviews()

    pricesLabel.sizeToFit()
    computedTotalLabel.sizeToFit()
}

How can I make the pricesLabel text adjusted with the line breaks size itself accordingly

class MyCell: UICollectionViewCell {

    let pricesLabel: UILabel = {
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.textAlignment = .right
        label.sizeToFit()
        label.font = UIFont.systemFont(ofSize: 15.5)
        label.adjustsFontSizeToFitWidth = true
        label.minimumScaleFactor = 0.5
        label.numberOfLines = 0
        label.sizeToFit()
        return label
    }()

    let computedTotalLabel: UILabel = {
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.textAlignment = .right
        label.textColor = .black
        label.sizeToFit()
        label.font = UIFont.boldSystemFont(ofSize: 15.5)
        label.adjustsFontSizeToFitWidth = true
        label.minimumScaleFactor = 0.5
        label.numberOfLines = 1
        label.sizeToFit()
        return label
    }()

    let staticTotalLabel: UILabel = {
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.text = "Total"
        label.textAlignment = .left
        label.textColor = .black
        label.font = UIFont.boldSystemFont(ofSize: 15.5)
        label.adjustsFontSizeToFitWidth = true
        label.minimumScaleFactor = 0.5
        label.numberOfLines = 1
        label.sizeToFit()
        return label
    }()

    let separatorLine: UIView = {
        let view = UIView()
        view.translatesAutoresizingMaskIntoConstraints = false
        view.backgroundColor = .lightGray
        return view
    }()

    override func layoutSubviews() {
        super.layoutSubviews()

        pricesLabel.sizeToFit()
        computedTotalLabel.sizeToFit()
    }

    var myObject: MyObject? {
        didSet {

           // text is "$8.00\n$12.00\n"
           let pricesText = myObject?.myText ?? "error"

           let attributedString = NSMutableAttributedString(string: pricesText, attributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 15.5)])
           let paragraphStyle = NSMutableParagraphStyle()
           paragraphStyle.lineSpacing = 7
           attributedString.addAttribute(.paragraphStyle, value: paragraphStyle, range: NSMakeRange(0, attributedString.length))

           pricesLabel.attributedText = attributedString

           computedTotalLabel.text = functionThatTalliesUpAllThePrices(pricesText)

           configureAnchors()
        }
    }

    func configureAnchors() {

        addSubview(pricesLabel)
        addSubview(totalLabel)
        addSubview(staticTotalLabel) // this is the label on the left side of the pic that says Total:
        addSubview(separatorLine)

        pricesLabel.topAnchor.constraint(equalTo: self.topAnchor, constant: 12).isActive = true
        pricesLabel.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -10).isActive = true

        staticTotalLabel.lastBaselineAnchor.constraint(equalTo: totalLabel.lastBaselineAnchor).isActive = true
        staticTotalLabel.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 10).isActive = true
        staticTotalLabel.rightAnchor.constraint(equalTo: totalLabel.leftAnchor, constant: -10).isActive = true

        computedTotalLabel.topAnchor.constraint(equalTo: pricesLabel.bottomAnchor, constant: 0).isActive = true
        computedTotalLabel.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -10).isActive = true

        separatorLine.topAnchor.constraint(equalTo: computedTotalLabel.bottomAnchor, constant: 12).isActive = true
        separatorLine.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 10).isActive = true
        separatorLine.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -10).isActive = true
        separatorLine.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
        separatorLine.heightAnchor.constraint(equalToConstant: 1).isActive = true
    }
}

This is the sizeForItem inside the collectionView cell. Not sure if this makes a difference to the problem so I added it anyway

class MyClass: UIViewController {

    let tableData = [MyObect]()

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

        let myObect = tableData[indexPath.item]

        // text is "$8.00\n$12.00\n"
        let pricesText = myObject?.myText ?? "error"

        let width = collectionView.frame.width

        let pricesLabelHeight = estimatedLabelHeight(text: pricesText, width: width, font: UIFont.systemFont(ofSize: 15.5))

        let total = functionThatTalliesUpAllThePrices(pricesText)
        let totalLabelHeight = estimatedLabelHeight(text: functionThatAddsUp, width: width, font: UIFont.boldSystemFont(ofSize: 15.5))

        // the 12 + 0 + 12 + 1 are the constant sizes I use inside the cell's configureAnchors functions
        let cellHeight = 12 + pricesLabelHeight + 0 + totalLabelHeight + 12 + 1

        return CGSize(width: width, height: ceil(cellHeight))
    }
}
Lance Samaria
  • 17,576
  • 18
  • 108
  • 256

1 Answers1

0

1st. I had to place the same estimatedLabelHeight(text: String, width: CGFloat, font: UIFont) inside the collectionView cell itself.

2nd. Inside the configureAnchors functions at the bottom of it I call pricesLabel.sizeToFit() and pricesLabel.layoutSubviews() and then I call the above function from step 1 to get the height of the pricesLabel from it's text.

3rd. I set the pricesLabel.heightAnchor.constraint(equalToConstant:) to the the height returned from step 2.

class MyCell: UICollectionViewCell {

    // step 1. place this function inside the collectionView cell
    func estimatedLabelHeight(text: String, width: CGFloat, font: UIFont) -> CGFloat {

        let size = CGSize(width: width, height: 1000)

        let options = NSStringDrawingOptions.usesFontLeading.union([.usesLineFragmentOrigin, .usesFontLeading])

        let attributes = [NSAttributedStringKey.font: font]

        let rectangleHeight = String(text).boundingRect(with: size, options: options, attributes: attributes, context: nil).height

        return rectangleHeight
    }

    func configureAnchors() {

        // all the other anchors are here

        pricesLabel.sizeToFit()
        computedTotalLabel.sizeToFit()

        computedTotalLabel.layoutIfNeeded()
        pricesLabel.layoutIfNeeded()

        let pricesLabelText = pricesLabel.text ?? "error"

        let width = self.frame.width

        // step 2.
        let pricesLabelHeight = estimatedLabelHeight(text: pricesLabelText, width: width, font: UIFont.systemFont(ofSize: 15.5))

        // step 3.
        pricesLabel.heightAnchor.constraint(equalToConstant: pricesLabelHeight).isActive = true
    }
}
Lance Samaria
  • 17,576
  • 18
  • 108
  • 256