Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UILabel size calculated incorrectly when changing font in UILabel

I am adding a UILabel to a view with the following layout requirements:

  • The label should be centered
  • The label should have a maximum font size of 100 points, but should scale down to fit. It should not truncate.
  • The label's height should not exceed 450 points.
  • Another view will be positioned directly below the label.

My label's properties layout constraints seem to describe this adequately:

let label = UILabel()
label.backgroundColor = UIColor.yellowColor()
label.font = UIFont.systemFontOfSize(100)
label.numberOfLines = 0
label.textAlignment = .Center
label.adjustsFontSizeToFitWidth = true
label.text = "What's Brewing in PET/CT: CT and MR Emphasis"
label.setTranslatesAutoresizingMaskIntoConstraints(false)
view.addSubview(label)

view.addConstraint(NSLayoutConstraint(item: label, attribute: .CenterX, relatedBy: .Equal, toItem: view, attribute: .CenterX, multiplier: 1, constant: 0))
view.addConstraint(NSLayoutConstraint(item: label, attribute: .CenterY, relatedBy: .Equal, toItem: view, attribute: .CenterY, multiplier: 1, constant: 0))
view.addConstraint(NSLayoutConstraint(item: label, attribute: .Left, relatedBy: .Equal, toItem: view, attribute: .Left, multiplier: 1, constant: 60))
view.addConstraint(NSLayoutConstraint(item: label, attribute: .Right, relatedBy: .Equal, toItem: view, attribute: .Right, multiplier: 1, constant: -60))
view.addConstraint(NSLayoutConstraint(item: label, attribute: .Height, relatedBy: .LessThanOrEqual, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 450))

However, I am finding that with longer content like that below, the label is displayed with some unexpected margins:

enter image description here

This is unacceptable since any views placed directly above or below the label will have a gap.

Based on the discussion below, my working theory is that the layout engine is calculating the size based on the font size of 100, without taking into account the subsequent scaling when the content is longer.

How can I meet the layout requirements listed above (ideally without using any deprecated methods)?

Here's a test project to play with.

like image 424
Ben Packard Avatar asked May 05 '15 19:05

Ben Packard


2 Answers

I took a look at your project and I might have found out a solution to your problem.

I added the following to the label setup:

label.minimumScaleFactor = 0.5

This is how the view looks like after my change: enter image description here

There is no more spacing on top of the label.

Hope this helps you with your problem! Let me know how it went.

like image 58
Catalina T. Avatar answered Nov 15 '22 00:11

Catalina T.


Had a look at your test project you kindly added.

The problem is that the font you are setting is too big (100pt) and the 450 point height can not be satisfied. You have set label.adjustsFontSizeToFitWidth = true which is causing it to try and fit it in as best it can by using a smaller font.

So when you set the font to 100, it is sizing the text area based on fitting the original text truncated and then it compresses it by changing the font to fit the width. This leaves a gap at the top and bottom after the compression as the size was based on the original font.

If you use smaller sizes like 90, it works fine as it fits in less than 450.

If you remove adjustsFontSizeToWith it works fine and fills the space, but it truncates the text.

If you remove the height constraint it works fine, but its height goes > 450.

So your problem is simply that the test font is too big for 450 height.

Possible Solution:

Ask the label for the font size needed using the answer from here: How to get UILabel (UITextView) auto adjusted font size?

Basically you use:

CGFloat actualFontSize;
[label.text sizeWithFont:label.font
             minFontSize:label.minimumFontSize
          actualFontSize:&actualFontSize
                forWidth:label.bounds.size.width
           lineBreakMode:label.lineBreakMode];

This is deprecated but that does not mean you cant use it for now. You need to convert it to swift.

Call this and if the font size is less than the original, set the font in the label the lower size. This in theory should give you a perfect fit as it should resize to the new font size you set.

Possible Solution Update:

I had a search for the swift equivalent of sizeWithFont and there are many articles but none I could see which replaced the version of the function noted above.

A slightly inefficient solution would be to add the following before your addSubview code in the test project you posted(excuse my swift). It basically searches backwards in font size until the font fits the known limits. This could obviously be made more efficient, but it shows that if you calculate the correct font before layout, then it will all fit perfectly at 450.0 height.

Tested this in your project and the text fits perfectly in the event of it originally not fitting at 450.0

    // The current font size and name
    var fontSize:CGFloat = 120
    let fontName:String = "AvenirNext-Regular"

    // The size to fit is the frame width with 60 margin each size and height
    // of 450
    let fitSize:CGSize = CGSize(width:view.frame.width-120.0,height:450)
    var newSize:CGSize

    do{
        // Create the trial font
        label.font = UIFont(name: fontName, size: fontSize)

        // Calculate the size this would need based on the fitSize we want
        newSize = label.sizeThatFits(fitSize)

        // Make the font size smaller for next time around.
        fontSize-=0.5
    } while (newSize.height >= 450)

    println("Font size had to be reduced to \(fontSize)")

    view.addSubview(label)
like image 39
Rory McKinnel Avatar answered Nov 14 '22 23:11

Rory McKinnel