skip to Main Content

I have a single-line UILabel with the following configuration:

let label = UILabel()
label.text = "Lorem ipsum dolor sit amet"
label.numberOfLines = 1

When I call sizeThatFits(size) on this label with a small size width, the result is always a size with a bigger width.

let labelSize = label.sizeThatFits(CGSize(width: 30, height: UILabel.noIntrinsicMetric))
// labelSize.width > 30

Shouldn’t sizeThatFits() return a size that is smaller or equal than the fitting size?

EDIT:

Neither of the suggested answers clarify this question.

Calling textRect(forBounds:limitedToNumberOfLines:) returns a zero width and systemLayoutSizeFitting() returns the same size as sizeThatFits(). There is no other workaround that can be used in any of the suggested answers.

2

Answers


  1. From Apple’s docs:

    Discussion

    The default implementation of this method returns the existing size of the view. Subclasses can override this method to return a custom value based on the desired layout of any subviews. For example, a UISwitch object returns a fixed size value that represents the standard size of a switch view, and a UIImageView object returns the size of the image it is currently displaying.

    This method does not resize the receiver.

    When we do this:

    let label = UILabel()
    label.text = "Lorem ipsum dolor sit amet"
    label.numberOfLines = 1
    
    let sz = label.sizeThatFits(.init(width: 30.0, height: UILabel.noIntrinsicMetric))
    

    UIKit says: "hey view (label), what is your size if I want a width of 30?" And the view (label) says: "I can’t fit in a width of 30, so, here is my sizeThatFits".

    In this case, on an iPhone 15 Pro, that is: (207.0, 20.333333333333332)

    If we call it like this:

    let sz = label.sizeThatFits(.init(width: 1000.0, height: 1000.0))
    

    The label says: "I can fit in a width of 1000, so here is my sizeThatFits"* — and we get the same result: (207.0, 20.333333333333332)

    So, what’s the point of the width/height values on .sizeThatFits()?

    Well, these days, we’re almost certainly using auto-layout / constraints, and we wouldn’t call that. However, there are valid use-cases to set the .frame of a label.

    Suppose I want a 30-point width? I do this:

    label.frame = .init(x: 20.0, y: 20.0, width: 30.0, height: ???)
    

    What value do I use for the height?

    // width doesn't really matter here... all I want is the Height
    let sz = label.sizeThatFits(.init(width: 30.0, height: UILabel.noIntrinsicMetric))
    label.frame = .init(x: 20.0, y: 20.0, width: 30.0, height: sz.height)
        
    

    (personally, I would use ceil(sz.height))

    Note that if .numberOfLines = 0 we get a very different return:

    label.numberOfLines = 0
    
    let sz = label.sizeThatFits(.init(width: 30.0, height: UILabel.noIntrinsicMetric))
        
    print(".numberOfLines = 0")
    print(".sizeThatFits(CGSize(width: 30, height: UILabel.noIntrinsicMetric))")
    print("sz:", sz)
    

    Now, UIKit says: "hey view (label), what is your size if I want a width of 30?" And the view (label) says: "I can wrap my text, so here is my sizeThatFits for a width of 30".

    we get this output:

    .numberOfLines = 0
    .sizeThatFits(CGSize(width: 30, height: UILabel.noIntrinsicMetric))
    sz: (28.333333333333332, 182.66666666666666)
    
    Login or Signup to reply.
  2. The documentation says:

    The intrinsic content size for a label defaults to the size that displays the entirety of the content on a single line.

    The whole point of sizeThatFits(_:) is “tell me what size I need to show all of this text.” The width parameter is essentially ignored if numberOfLines is 1.

    The width supplied to sizeThatFits(_:) only comes into play when using numberOfLines of 0 (or greater than 1). In that scenario, sizeThatFits(_:) will now let us know the label’s height (and width) that will fit the text wrapped within the originally supplied width.

    I understand from your comments that you were expecting it to never return a value whose width that exceeded 30 even if numberOfLines is 1. Right or wrong, that simply is not how it works. If you want to clamp the width to not exceed 30, you would do this yourself:

    let label = UILabel()
    label.text = "Lorem ipsum dolor sit amet"
    label.numberOfLines = 1
    
    var size = label.sizeThatFits(CGSize(width: 30, height: UILabel.noIntrinsicMetric))
    size.width = min(size.width, 30)
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search