Search code examples
swiftnsmutableattributedstring

How to get the estimated size of a rectangle required to draw NSMutableAttributedString?


I am trying to get the estimated size of the rectangle required to draw out a NSMutableAttributedString. The numbers that come not does not make any sense to me. I have a UIViewController with a UIlabel (txtField), with a UIlabel.numberOfLines = 3. I would like to estimate the height of this NSMutableAttributedString were i to set UIlabel.numberOfLines= 0.

With reference to the console reading, I do not understand why the estimated height of the rectangle required to draw the entire NSMutableAttributedString is less than that if it were constrained to just 3 lines?

var txtField: UILabel = {
    let label = UILabel()
    label.numberOfLines = 3
    label.translatesAutoresizingMaskIntoConstraints = false
    label.lineBreakMode = .byTruncatingTail
    return label
}()


override func viewDidLoad() {
    super.viewDidLoad()

    let content = "Pasture he invited mr company shyness. But when shot real her. Chamber her 
     observe visited removal six sending himself boy. At exquisite existence if an oh dependent excellent. Are gay head need down draw. Misery wonder enable mutual get set oppose the uneasy. End why melancholy estimating her had indulgence middletons. Say ferrars demands besides her address. Blind going you merit few fancy their. "

    let attributes = [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 14)]
    let attributedString = NSMutableAttributedString.init(string: content, attributes: attributes)

    let paragraphStyle = NSMutableParagraphStyle()
    paragraphStyle.lineSpacing = 1.5
    paragraphStyle.lineBreakMode = .byTruncatingTail
    attributedString.addAttributes([.paragraphStyle : paragraphStyle], range: NSRange(location: 0, length: attributedString.length))
    txtField.attributedText = attributedString

}


override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()

    if let attText = txtField.attributedText{
        let size = CGSize.init(width: txtField.frame.width - 20, height: 1000)
        let estimatedFrame = attText.boundingRect(with: size, options: .usesLineFragmentOrigin, context: nil)
        print("txtField.frame: \(txtField.frame)")
        print("estimatedFrame: \(estimatedFrame)")

    }
}

CONSOLE:

txtField.frame: (0.0, 0.0, 394.0, 53.333333333333336)
estimatedFrame: (0.0, 0.0, 367.28125, 16.70703125)

Solution

  • This is wrong:

    paragraphStyle.lineBreakMode = .byTruncatingTail
    

    Attributed string line breaking is different from label line breaking. Your attributed string needs to have a line break mode that wraps. Otherwise you are measuring the height of just one line.