Mert Diricanlı Mert Diricanlı - 11 months ago 113
iOS Question

Getting UILabel height with multiple lines in Swift

I'm trying to calculate the height of my UILabel to make the page scrollable. The label contains attributedText to display HTML content.

I'm using this function to get my HTML content to the UILabel:

func stringFromHTML( string: String?) -> NSAttributedString
let pStyle = NSMutableParagraphStyle()
pStyle.lineSpacing = 4
pStyle.paragraphSpacingBefore = 10

let str = try NSMutableAttributedString(data:string!.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true
)!, options:[NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, NSCharacterEncodingDocumentAttribute: NSNumber(unsignedLong: NSUTF8StringEncoding)], documentAttributes: nil)
str.addAttribute(NSParagraphStyleAttributeName, value: pStyle, range: NSMakeRange(0, str.length))
str.addAttribute(NSFontAttributeName, value: UIFont(name: "Helvetica Neue", size: 16.0)!, range: NSMakeRange(0, str.length))

return str
} catch
print("html error\n",error)
return NSAttributedString(string: "")

I'm a newbie. So, I make a research and find this extention to get the height of the UILabel. It returns a value but I guess it's not true. My UIScrollView doesn't seem to work:

extension UILabel{
func requiredHeight() -> CGFloat{
let label:UILabel = UILabel(frame: CGRectMake(0, 0, self.frame.width, CGFloat.max))
label.numberOfLines = 0
label.lineBreakMode = NSLineBreakMode.ByWordWrapping
label.font = self.font
label.text = self.text


return label.frame.height

And this is my code in the viewDidLoad() method for the constraints:

// Set ContentView height
let activityTitleHeight:CGFloat = self.activityTitle.requiredHeight() + 20
let activityDescHeight:CGFloat = self.activityDesc.requiredHeight() + 20
let totalContentHeight:CGFloat = activityDescHeight + activityTitleHeight + 345
// There is an image view and a navigation, their total height: 345

let contentViewHeight:NSLayoutConstraint = NSLayoutConstraint(item: self.contentView, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1, constant: totalContentHeight)

Thanks in advance!

Answer Source

Since you want entire page to become scrollable once the HTML content is larger than the size of the application's window, I guess your top level view is UIScrollView.

The main trick here is to set height constraint of the label displaying the HTML to 0 and then update it programmatically once you calculate the height of the HTML text it contains.

If the text being displayed is too long, then the scroll view will start scrolling automatically provided that you set vertical constraints correctly.

By the way, it is an overkill to create a UILabel just to calculate the height of a string. As of iOS7, we have NSAttributedString.boundingRect(size:options:context) method which calculates the minimum rect to draw the text with given options as follows:

string.boundingRectWithSize(CGSizeMake(width, CGFloat.max), options: [.UsesFontLeading, .UsesLineFragmentOrigin], context: nil)

Going back to your question, you should create an ivar of NSLayoutConstraint type representing the height of the label in question:

class ViewController: UIViewController {
  @IBOutlet weak var htmlLabel: UILabel!
  private var labelHeightConstraint: NSLayoutConstraint!

and set its constant property to 0 as we said earlier:

override func viewDidLoad() {

  htmlLabel.text = stringFromHTML(htmlText)

  labelHeightConstraint = NSLayoutConstraint(item: htmlLabel, attribute: .Height, relatedBy: .Equal,
                                             toItem: nil, attribute: .NotAnAttribute, multiplier: 0, constant: 0)

Then calculate the height of the label:

(Since we don't have the exact width of any superview in viewDidLoad, viewDidAppear is a good place to do such calculation.)

override func viewDidAppear(animated: Bool) {

  let maxSize = CGSizeMake(CGRectGetWidth(scrollView.frame), CGFloat.max)
  let textSize = htmlLabel.attributedText!.boundingRectWithSize(maxSize, options: [.UsesFontLeading, .UsesLineFragmentOrigin], context: nil)

  labelHeightConstraint.constant = ceil(textSize.height)

I created a test project for you showing what I did above. You can download it from here.