iOS: Multi-line UILabel in Auto Layout

127,285

Solution 1

Use -setPreferredMaxLayoutWidth on the UILabel and autolayout should handle the rest.

[label setPreferredMaxLayoutWidth:200.0];

See the UILabel documentation on preferredMaxLayoutWidth.

Update:

Only need to set the height constraint in storyboard to Greater than or equal to, no need to setPreferredMaxLayoutWidth.

Solution 2

Expand your label set number of lines to 0 and also more importantly for auto layout set height to >= x. Auto layout will do the rest. You may also contain your other elements based on previous element to correctly position then.

auto layout

Solution 3

Source: http://www.objc.io/issue-3/advanced-auto-layout-toolbox.html

Intrinsic Content Size of Multi-Line Text

The intrinsic content size of UILabel and NSTextField is ambiguous for multi-line text. The height of the text depends on the width of the lines, which is yet to be determined when solving the constraints. In order to solve this problem, both classes have a new property called preferredMaxLayoutWidth, which specifies the maximum line width for calculating the intrinsic content size.

Since we usually don’t know this value in advance, we need to take a two-step approach to get this right. First we let Auto Layout do its work, and then we use the resulting frame in the layout pass to update the preferred maximum width and trigger layout again.

- (void)layoutSubviews
{
    [super layoutSubviews];
    myLabel.preferredMaxLayoutWidth = myLabel.frame.size.width;
    [super layoutSubviews];
}

The first call to [super layoutSubviews] is necessary for the label to get its frame set, while the second call is necessary to update the layout after the change. If we omit the second call we get a NSInternalInconsistencyException error, because we’ve made changes in the layout pass which require updating the constraints, but we didn’t trigger layout again.

We can also do this in a label subclass itself:

@implementation MyLabel
- (void)layoutSubviews
{
    self.preferredMaxLayoutWidth = self.frame.size.width;
    [super layoutSubviews];
}
@end

In this case, we don’t need to call [super layoutSubviews] first, because when layoutSubviews gets called, we already have a frame on the label itself.

To make this adjustment from the view controller level, we hook into viewDidLayoutSubviews. At this point the frames of the first Auto Layout pass are already set and we can use them to set the preferred maximum width.

- (void)viewDidLayoutSubviews
{
    [super viewDidLayoutSubviews];
    myLabel.preferredMaxLayoutWidth = myLabel.frame.size.width;
    [self.view layoutIfNeeded];
}

Lastly, make sure that you don’t have an explicit height constraint on the label that has a higher priority than the label’s content compression resistance priority. Otherwise it will trump the calculated height of the content. Make sure to check all the constraints that can affect label's height.

Solution 4

I was just fighting with this exact scenario, but with quite a few more views that needed to resize and move down as necessary. It was driving me nuts, but I finally figured it out.

Here's the key: Interface Builder likes to throw in extra constraints as you add and move views and you may not notice. In my case, I had a view half way down that had an extra constraint that specified the size between it and its superview, basically pinning it to that point. That meant that nothing above it could resize larger because it would go against that constraint.

An easy way to tell if this is the case is by trying to resize the label manually. Does IB let you grow it? If it does, do the labels below move as you expect? Make sure you have both of these checked before you resize to see how your constraints will move your views:

IB Menu

If the view is stuck, follow the views that are below it and make sure one of them doesn't have a top space to superview constraint. Then just make sure your number of lines option for the label is set to 0 and it should take care of the rest.

Solution 5

I find you need the following:

  • A top constraint
  • A leading constraint (eg left side)
  • A trailing constraint (eg right side)
  • Set content hugging priority, horizontal to low, so it'll fill the given space if the text is short.
  • Set content compression resistance, horizontal to low, so it'll wrap instead of try to become wider.
  • Set the number of lines to 0.
  • Set the line break mode to word wrap.
Share:
127,285

Related videos on Youtube

James Harpe
Author by

James Harpe

Updated on July 10, 2022

Comments

  • James Harpe
    James Harpe almost 2 years

    I'm having trouble trying to achieve some very basic layout behavior with Auto Layout. My view controller looks like this in IB:

    enter image description here

    The top label is the title label, I don't know how many lines it will be. I need the title label to display all lines of text. I also need the other two labels and the small image to be laid out right below the title, however tall it happens to be. I have set vertical spacing constraints between the labels and small image, as well as a top spacing constraint between the title label and its superview and a bottom spacing constraint between the small image and its superview. The white UIView has no height constraint, so it should stretch vertically to contain its subviews. I have set the number of lines for the title label to 0.

    How can I get the title label to resize to fit the number of lines required by the string? My understanding is that I can't use setFrame methods because I'm using Auto Layout. And I have to use Auto Layout because I need those other views to stay below the title label (hence the constraints).

    How can I make this happen?

    • user3274901
      user3274901 over 11 years
      I am also fighting with a similar problem. But I am still struggling to get the top label to adjust its height dynamically to fit the content. How did you achieve this?
    • jemmons
      jemmons about 10 years
      Please consider marking @mwhuss's answer as the accepted one.
    • Aniruddha
      Aniruddha over 9 years
      Did you achieve the required result?
    • Badal Shah
      Badal Shah about 8 years
      check this , you dont need to add single line of code stackoverflow.com/a/36862795/4910767
  • James Harpe
    James Harpe over 11 years
    I want the different instances of the view controller to have a consistent appearance, so I don't want to change the font size.
  • James Harpe
    James Harpe over 11 years
    Yep, after a lot of playing around with it I was able to get the effect I wanted. You just have to think very carefully about the constraints you want. It doesn't help that IB is constantly throwing in constraints you don't expect.
  • Sjoerd Perfors
    Sjoerd Perfors over 10 years
    Nice one, anyway to do this in interface builder?
  • MANIAK_dobrii
    MANIAK_dobrii about 10 years
    @SjoerdPerfors looks like it just uses current width for it's value, not sure if that's always true, but it was so for me.
  • jowie
    jowie almost 10 years
    Also set the height constraint in IB to Greater than or equal to".
  • Tom Howard
    Tom Howard over 9 years
    If this method doesn't work for you, please see Cory Imdieke's answer about making sure subsequent views don't prohibit Auto Layout from resizing the (possibly) multiline view.
  • bgolson
    bgolson over 9 years
    This is the only answer that worked for me within a Table View Cell. Also works with a fixed number of rows (instead of 0/unlimited)
  • Li Fumin
    Li Fumin about 9 years
    Reminder: please remember to set numberOfLines property.
  • jmc
    jmc about 9 years
    This didn't work for me with an explicit preferred maximum width set so make sure this is set to automatic in IB when using this technique. Cheers
  • mwhuss
    mwhuss about 9 years
    Yep, use whatever you want the maximum width to be.
  • Tony
    Tony about 9 years
    Setting automatic max width is only available in iOS8. It also, from my understanding, will adjust the height automatically and in my experience you don't need to set a height constraint for it to work. This worked for me using an explicit width.
  • djibouti33
    djibouti33 about 9 years
    thank you thank you thank you for not only the code, but more importantly the explanation and the examples of how to do this not only in a subclass, but also from the view controller.
  • djibouti33
    djibouti33 about 9 years
    i'm attempting this in my view controller, and viewDidLayoutSubviews is getting called after viewWillAppear and viewDidAppear. After viewDidAppear, there's a flash when the labels get resized correctly. is there any way to avoid this? is there a way for resizing to be complete before the user sees anything? in my case, my multi-line labels are in a tableHeaderView, and so I'm also resizing the header view's height based on the labels using this example (stackoverflow.com/questions/20982558/…)
  • Anton Matosov
    Anton Matosov almost 9 years
    @djibouti33 can you please provide the minimum sample code (e.g. in format of github repo) so I can easily check your issue?
  • djibouti33
    djibouti33 almost 9 years
    Thanks @Anton. Our design has since switched from a UITableView to a UICollectionView, and fortunately I did not see this behavior. If I can get at my git history to create a small sample project, I'll let you know.
  • Abolfoooud
    Abolfoooud over 8 years
    I found this useful in my situation but only if I have set the content compression resistance priority to 1000 so that it grows as I wanted
  • troppoli
    troppoli over 8 years
    It would be nice to know why this works, I fear it might be a "bug" that gets "fixed".
  • tboyce12
    tboyce12 over 8 years
    See stackoverflow.com/a/29180323/1529675 for how to determine preferredMaxLayoutWidth and see devetc.org/code/2014/07/07/auto-layout-and-views-that-wrap.h‌​tml for a brief but informative writeup, complete with animated GIFs (not my writeup, I found it through Google)
  • tboyce12
    tboyce12 over 8 years
    This didn't work for me. I believe stackoverflow.com/a/13032251/1529675 is the correct answer.
  • naz
    naz over 8 years
    An issue for me since I started running on an iOS 9 sim but when I tested on iOS 8 then the trouble started. I need to set the max width manually.
  • Archagon
    Archagon over 8 years
    Huh. I was having a problem where the labels in my cells were reverting to one line after going off screen, but not when they first appeared. This fixed it right up. Magic!
  • Brainware
    Brainware over 7 years
    Important! You must constrain the width too (trailing/leading space or actual width). Otherwise, the label will grow horizontally and wrapping won't occur.
  • Anton Tropashko
    Anton Tropashko over 6 years
    This should work in xcode9. I did not have to do anything with the label height constraint (I have none): it just works out of the box lately once the label will have been constrained (trailing, leading, top and bottom). uikit does the rest
  • David Gay
    David Gay about 6 years
    The content hugging priority was my problem. Thank you for making me aware of that property as well as compression resistance. You helped me resolve an hours-long problem.