ChatGPT解决这个技术问题 Extra ChatGPT

iOS: Multi-line UILabel in Auto Layout

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

https://i.stack.imgur.com/pg6fl.png

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?

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?
Please consider marking @mwhuss's answer as the accepted one.
Did you achieve the required result?
check this , you dont need to add single line of code stackoverflow.com/a/36862795/4910767

B
Bruno Bieri

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.


Nice one, anyway to do this in interface builder?
Also set the height constraint in IB to Greater than or equal to".
Reminder: please remember to set numberOfLines property.
Yep, use whatever you want the maximum width to be.
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.html for a brief but informative writeup, complete with animated GIFs (not my writeup, I found it through Google)
m
mfaani

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.

https://i.stack.imgur.com/wzfxk.png


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.
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)
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
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.
This didn't work for me. I believe stackoverflow.com/a/13032251/1529675 is the correct answer.
A
Anton Matosov

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.


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.
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/…)
@djibouti33 can you please provide the minimum sample code (e.g. in format of github repo) so I can easily check your issue?
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.
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.
C
Cory Imdieke

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:

https://i.stack.imgur.com/ot9G6.png

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.


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.
C
Chris

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.


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
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.
T
Tanguy G.

None of the different solutions found in the many topics on the subject worked perfectly for my case (x dynamic multiline labels in dynamic table view cells) .

I found a way to do it :

After having set the constraints on your label and set its multiline property to 0, make a subclass of UILabel ; I called mine AutoLayoutLabel :

@implementation AutoLayoutLabel

- (void)layoutSubviews{
    [self setNeedsUpdateConstraints];
    [super layoutSubviews];
    self.preferredMaxLayoutWidth = CGRectGetWidth(self.bounds);
}

@end

A
Alvin George

I have a UITableViewCell which has a text wrap label. I worked text wrapping as follows.

1) Set UILabel constraints as follows.

https://i.stack.imgur.com/ODSQP.png

2) Set no. of lines to 0.

3) Added UILabel height constraint to UITableViewCell.

@IBOutlet weak var priorityLabelWidth: NSLayoutConstraint!

4) On UITableViewCell:

priorityLabel.sizeToFit()
priorityLabelWidth.constant = priorityLabel.intrinsicContentSize().width+5

M
Mureinik

One way to do this... As text length increases try to change (decrease) the fontsize of the label text using

Label.adjustsFontSizeToFitWidth = YES;

I want the different instances of the view controller to have a consistent appearance, so I don't want to change the font size.