UILabel sizeToFit doesn't work with autolayout ios6
Asked Answered
M

13

159

How am I supposed to configure programmatically (and in which method) a UILabel whose height depends on its text? I've been trying to set it up using a combination of Storyboard and code, but to no avail. Everyone recommends sizeToFit while setting lineBreakMode and numberOfLines. However, no matter if I put that code in viewDidLoad:, viewDidAppear:, or viewDidLayoutSubviews I can't get it to work. Either I make the box too small for long text and it doesn't grow, or I make it too big and it doesn't shrink.

Meares answered 15/4, 2013 at 7:1 Comment(1)
FWIW the same code: I didn't need to use label.sizeToFit() in Xcode/viewController, the constraints were enough. wasn't creating the label in Playground. So far the only way I found it to work in Playground is to do label.sizeToFit()Volnak
Z
409

Please note that in most cases Matt's solution works as expected. But if it doesn't work for you, please, read further.

To make your label automatically resize height you need to do following:

  1. Set layout constrains for label
  2. Set height constraint with low priority. It should be lower than ContentCompressionResistancePriority
  3. Set numberOfLines = 0
  4. Set ContentHuggingPriority higher than label's height priority
  5. Set preferredMaxLayoutWidth for label. That value is used by label to calculate its height

For example:

self.descriptionLabel = [[UILabel alloc] init];
self.descriptionLabel.numberOfLines = 0;
self.descriptionLabel.lineBreakMode = NSLineBreakByWordWrapping;
self.descriptionLabel.preferredMaxLayoutWidth = 200;

[self.descriptionLabel setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
[self.descriptionLabel setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
[self.descriptionLabel setTranslatesAutoresizingMaskIntoConstraints:NO];
[self addSubview:self.descriptionLabel];

NSArray* constrs = [NSLayoutConstraint constraintsWithVisualFormat:@"|-8-[descriptionLabel_]-8-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(descriptionLabel_)];
[self addConstraints:constrs];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-8-[descriptionLabel_]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(descriptionLabel_)]];
[self.descriptionLabel addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[descriptionLabel_(220@300)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(descriptionLabel_)]];

Using Interface Builder

  1. Set up four constraints. The height constraint is mandatory. enter image description here

  2. Then go to the label's attributes inspector and set number of lines to 0. enter image description here

  3. Go to the label's size inspector and increase vertical ContentHuggingPriority and vertical ContentCompressionResistancePriority.
    enter image description here

  4. Select and edit height constraint.
    enter image description here

  5. And decrease height constraint priority.
    enter image description here

Enjoy. :)

Zitazitah answered 15/4, 2013 at 7:20 Comment(9)
YES! This is exactly the answer I was looking for. The only thing I needed to do was set contentHuggingPriority and contentCompressionResistancePriority to required. I could do it all in IB! Thank you so much.Meares
You're overthinking this. Either set the preferredMaxLayoutWidth or pin the label's width (or its right and left sides). That's all! It will then automatically grow and shrink vertically to fit its contents.Swish
I had to set the preferredMaxLayoutWidth property + pin the label's width. Works like a charm :)Pancho
preferredMaxLayoutWidth and/or pinning the left and right sides is not absolute. Content hugging and compression priorities will always work as long as their priorities are higher than the rest of the constraints.Schell
What is especially valuable about this answer is that it works with a layout that has items below the label that constrain themselves to the dynamically-determined vertical position of the bottom of the label rectangle.Subsolar
None of this works anymore in Xcode 6, iOS8 ??? Should I go kick you know who´s grave?Dealt
I followed these exact steps, except that I put the height priority on 250 instead of 500. It still is one line from the height it should have. I do have 2 Labels under eachother, both can grow in size.Postcard
^ And I finally found out why, Might be worth to mention, when you have the cell sticking to the bottom of the view, you need to set the priority of the "to bottom" constraint to less then 1000 I put it on 750 and everything is working perfectly. I guess it shrunk the view.Postcard
For xcode 7, priority could be any value less than 1000(Required).Hasa
S
66

In iOS 6, using autolayout, if a UILabel's sides (or width) and top are pinned, it will automatically grow and shrink vertically to fit its contents, with no code at all and no messing with its compression resistance or whatever. It is dead simple.

In more complex cases, just set the label's preferredMaxLayoutWidth.

Either way, the right thing happens automatically.

Swish answered 4/5, 2013 at 0:20 Comment(5)
You also need to make sure to set numberOfLines to 0 otherwise you won't get wrapping.Roede
This wasn't enough for me. Also needed point 2 from @Mark's answer.Sanctuary
is it also possible to make label grows automatically on several lines without writing codes?Mariande
This is a lot less complicated than the accepted answer. I made two examples (here and here) illustrating this answer.Miff
@Miff Using constraints this works. But it doesn't work for the same code in Playground. In playground you must have label.sizeToFit()Volnak
O
42

Although the question states programmatically, having encountered the same problem, and preferring to work in Interface Builder, I thought it might be useful to add to the existing answers with an Interface Builder solution.

The first thing is to forget sizeToFit. Auto Layout will handle this on your behalf based upon the intrinsic content size.

The problem therefore is, how to get a label to fit it's content with Auto Layout? Specifically - because the question mentions it - height. Note that the same principles apply to width.

So let's start with an example UILabel that has a height set to 41px high:

enter image description here

As you can see in the screen grab above, "This is my text" has padding above and below. That is padding between the UILabel's height, and it's content, the text.

If we run the app in the simulator, sure enough, we see the same thing:

enter image description here

Now, let's select the UILabel in Interface Builder, and take a look at the default settings in the Size inspector:

enter image description here

Note the highlighted constraint above. That is the Content Hugging Priority. As Erica Sadun describes it in the excellent iOS Auto Layout Demystified, this is:

the way a view prefers to avoid extra padding around it's core content

For us, with the UILabel, the core content is the text.

Here we come to the heart of this basic scenario. We have given our text label two constraints. They conflict. One says "the height must be equal to 41 pixels high". The other says "hug the view to it's content so we don't have any extra padding". In our case, hug the view to it's text so we don't have any extra padding.

Now, with Auto Layout, with two different instructions that say do different things, the runtime has to choose one or the other. It can't do both. The UILabel can't be both 41 pixels high, and have no padding.

The way this is resolved, is by specifying priority. One instruction has to have a higher priority than the other. If both instructions say different things, and have the same priority, an exception will occur.

So let's give it a go. My height constraint has a priority of 1000, which is required. Content hugging height is 250, which is weak. What happens if we reduce the height constraint priority to 249?

enter image description here

Now we can see the magic start to happen. Let's try in the sim:

enter image description here

Awesome! Content hugging achieved. Only because height priority 249 is less than content hugging priority 250. Basically, I'm saying "the height I specify here is less important than what I've specified for the content hugging". So, the content hugging wins.

Bottom line, getting the label to fit the text can be as simple as specifying the height - or width - constraint, and correct setting that priority in association with that axis' content hugging priority constraint.

Will leave doing the equivalent for width as an exercise for the reader!

Ocher answered 29/10, 2013 at 12:6 Comment(3)
Thanks so much for your great explanation! Finally I understand Content Hugging Priority.Inspan
Could you please explain also what is Content Compression Resistance Priority? Thanks!Inspan
Excelente answer Max! Is there someway to limit the number of lines working that way?Condonation
G
7

Noticed in IOS7 sizeToFit wasn't working also - perhaps the solution may help you too

[textView sizeToFit];
[textView layoutIfNeeded];
Gaga answered 25/9, 2013 at 13:52 Comment(2)
@Rambatino This works to me. Add layoutIfNeeded when you need setNeedsUpdateConstraints. But the situation might be a little different.Hobby
This worked best for me when I did a popover. I needed to do layoutIfNeeded first, thoughMinium
G
4

Another option for ensuring the label's preferredMaxLayoutWidth is kept in sync with the label's width:

#import "MyLabel.h"

@implementation MyLabel

-(void)setBounds:(CGRect)bounds
{
    [super setBounds:bounds];

    // This appears to be needed for iOS 6 which doesn't seem to keep
    // label preferredMaxLayoutWidth in sync with its width, which 
    // means the label won't grow vertically to encompass its text if 
    // the label's width constraint changes.
    self.preferredMaxLayoutWidth = self.bounds.size.width;
}

@end
Gewirtz answered 19/3, 2014 at 7:59 Comment(0)
A
4

I feel I should contribute as it took me a while to find the right solution:

  • The goal is to let Auto Layout do its work without ever calling sizeToFit(), we will do this by specifying the right constraints:
  • Specify top, bottom, and leading/trailing space constraints on your UILabel
  • Set the number of lines property to 0
  • Increment the Content Hugging Priority to 1000
  • Lower the Content Compression Resistance Priority to 500
  • On your bottom container constraint, lower the priority to 500

Basically, what happens is that you tell your UILabel that even though it has a fixed height constraint, it can make break the constraint to make itself smaller in order to hug the content (if you have a single line for example), but it cannot break the constraint to make it larger.

Alphosis answered 15/5, 2015 at 0:10 Comment(0)
C
3

In my case I was creating a UIView subclass that contained a UILabel (of unknown length). In iOS7 the code was straightforward: set the constraints, don't worry about content hugging or compression resistance, and everything worked as expected.

But in iOS6 the UILabel was always clipped to a single line. None of the answers above worked for me. Content hugging and compression resistance settings were ignored. The only solution that prevented clipping was to include a preferredMaxLayoutWidth on the label. But I did not know what to set the preferred width to, as the size of its parent view was unknown (indeed, it would be defined by the contents).

I finally found the solution here. Because I was working on a custom view, I could just add the following method to set the preferred layout width after the constraints had been calculated once, and then recalculate them:

- (void)layoutSubviews
{
    // Autolayout hack required for iOS6
    [super layoutSubviews];
    self.bodyLabel.preferredMaxLayoutWidth = self.bodyLabel.frame.size.width;
    [super layoutSubviews];
}
Clemenciaclemency answered 31/10, 2013 at 5:18 Comment(2)
This was the exact scenario I had…sometimes it's nice to know you're not alone.Othella
Adam, I'm struggling with what you said worked perfectly in ios7. I have a custom cell with a uiview and a uilabel. I can get the label to fit content but the view doesn't, no matter what constraints I put on it. How did you get it to work? I swear I tried everything but it doesn't take the height of the labelDelphinus
G
3

I added UILabel programmatically and in my case that was enough:

label.translatesAutoresizingMaskIntoConstraints = false
label.setContentCompressionResistancePriority(UILayoutPriorityRequired, forAxis: .Vertical)
label.numberOfLines = 0
Gemperle answered 9/3, 2016 at 19:11 Comment(0)
E
2

i have solved with xCode6 putting "Preferred Width" to Automatic and pin the label top, leading and trailing enter image description here

Eusporangiate answered 1/10, 2014 at 9:21 Comment(0)
U
0
UIFont *customFont = myLabel.font;
CGSize size = [trackerStr sizeWithFont:customFont
                             constrainedToSize:myLabel.frame.size // the size here should be the maximum size you want give to the label
                                 lineBreakMode:UILineBreakModeWordWrap];
float numberOfLines = size.height / customFont.lineHeight;
myLabel.numberOfLines = numberOfLines;
myLabel.frame = CGRectMake(258, 18, 224, (numberOfLines * customFont.lineHeight));
Unbuckle answered 15/4, 2013 at 7:28 Comment(0)
C
0

I ran into this problem as well with a UIView subclass that contains a UILabel as one if its internal elements. Even with autolayout and trying all of the recommended solutions, the label just wouldn't tighten its height to the text. In my case, I only ever want the label to be one line.

Anyway, what worked for me was to add a required height constraint for the UILabel and set it manually to the correct height when intrinsicContentSize is called. If you don't have the UILabel contained in another UIView, you could try subclassing UILabel and provide a similar implementation by first setting the height constraint and then returning
[super instrinsicContentSize]; instead of [self.containerview intrinsiceContentSize]; like I do below which is specific to my UIView sublass.

- (CGSize)intrinsicContentSize
{
    CGRect expectedTextBounds = [self.titleLabel textRectForBounds:self.titleLabel.bounds limitedToNumberOfLines:1];
    self.titleLabelHeightConstraint.constant = expectedTextBounds.size.height;
    return [self.containerView intrinsicContentSize];

}

Works perfectly now on iOS 7 and iOS 8.

Casino answered 4/2, 2015 at 12:57 Comment(3)
It's worth noting that if you have set up your view correctly (i.e. your constraints are correct and you've set everything up during the correct stages in the view's lifecycle), you should never have to override intrinsicContentSize. The runtime should be able to calculate this based on your correctly setup constraints.Colugo
In theory that should be correct, but we do not have access to all of Apple's private APIs and implementations and they are not infallible and do have bugs in their code.Casino
...but [super intrinsicContentSize] should take care of it, is what I'm saying. If you've implemented your auto layout correctly.Colugo
L
0

A solution that worked for me; If your UILabel has a fixed width, change the constraint from constant = to constant <= in your interface file

enter image description here

Lisandra answered 18/8, 2017 at 8:11 Comment(0)
F
-1

In my case when using the labels in a UITableViewCell, the label at would resize but the height would overrun the table cell height. This is what worked for me. I did according to Max MacLeod, and then made sure cell height was set to UITableViewAutomaticDimension.

You can add this is in your init or awakeFromNib,

self.tableView.rowHeight = UITableViewAutomaticDimension.  

In the storyboard, select the cell, open the Size inspector, and make sure row height is set to "Default" by uchecking the 'Custom' checkbox.

There is an issue in 8.0 that also requires it to be set in code.

Forestaysail answered 5/11, 2014 at 20:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.