iOS: Multi-line UILabel in Auto Layout - ios

I'm having trouble trying to achieve some very basic layout behavior with Auto Layout. My view controller looks like this in IB:
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?

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.

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.

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.

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:
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.

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.

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

I have a UITableViewCell which has a text wrap label. I worked text wrapping as follows.
1) Set UILabel constraints as follows.
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

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

Related

Make Vertical Space Constraint Constant equal to percentage of screen height

I want the Vertical Spacing Constraint Constant value between two of my subviews to be a percentage of the screen height. This way the design looks about the same on all devices.
How do I do that in storyboard? Any well known tricks?
Perhaps there is some trick to achieve this with content hugging priority, compression resistance, or a second constraint of a lesser priority?
The solution I had in mind is to introduce a third subview to sit in between my two subviews - and set an aspect height constraint on that third subview with the superview. The two subviews would have a space constraint with the third subview with a constant of zero.
However - I don't like having a storyboard cluttered with make-belief invisible subviews.
create an IBOutlet of your vertical space constraints.then check what is the device using screen size then assign value to your constraint's constant. EX:
if(screenSize.height == 480)
{
self.verticalspaceConstraints.constant = 100 ;//this is an example
}
//like this add your other conditions
I have created a small video tutorial to get the basic idea try this
small video tutorial
hope this will help to you.
I have 2 solutions but am using the first one.
1) Put a UIView with the desired gap height between the two elements and give the gap view as proportional height to the main view of controller. This way it will increase according to device but this idea may feels complex. Because to design a complex screen you will have to put lots of gap view and it will be messy.
2) Mark the constraint in design time to be dynamic by assigning some character to the identifier property of the constraint. Create a category class of NSLayoutConstraint and in the class inherit its function and check if the identifier is started with that character and modify the value like multiplying with device scaling.
To have a adapting vertical spacing between two views, you can :
Use UIStackview and define spacing dynamically
Use Vertical Spacing Constraint between the two and change priority to 250. You need to have top constraint on the first and bottom on the second.
Use Equal Height relation between each subviews and superview. You set "proportional height" with like 0.4 on each, which let you 20% margin between. To do so, CTRL + clic on subviews then draw line to superview.
Then adjust "multiplier" making sure that the first element is the subview.

Allow UILabel to grow dynamically using auto layout

I have a label which is going to contain a big description. I want the label to continue growing on new lines. In the image, its the label which starts with event_venue.....
The even_venue.. label has 3 constraints for now:
Vertical space with eventt_title
a horizantal space with the leading of the superview
a width constraints which defines that the label width is always less than the superview.width.
What I want to acheive is to make the event_venue.width less than superview.width, but if it has more text, it should display in new lines. Is this possible using autolayout?
This are possible steps which can create expandable UILabel
Set layouts for UILabel fixing its position
Set number of lines = 0
Set content vertical compression resistance to 1000 (this will allow text to push the label)
Since you want UILabel to expand you cannot give it fixed height constraint or its parent fixed height constraint. Sometimes depending upon condition giving height constraint is necessary to avoid error then you need to set its priority lower than vertical compression resistance
Yes, this totally is possible. I see answers here that are close to solution but not complete. Here is a solution which works with auto layout in Storyboard, no coding of sizeToFit or anything. Your modified steps would be:
Vertical space with eventt_title
A horizontal space with the leading of the superview
A horizontal space with the trailing of the superview
Set UILabel's Line Breaks as Word Wrap.
Set UILabel's lines property as 0.
I have solved a similar problem. I had to make a label that had a variable amount of text. Here's what I did:
In the storyboard, place your label with the origin where you want it.
In the Attributes Inspector, "Label" section, set the Line Breaks = Word Wrap
Fill the label with random placeholder text to the maximum shape you want. For example, if you wanted to fill the whole width and have room for a maximum of three lines of text, you could do:
abcdefghijklmnopqrstu
abcdefghijklmnopqrstu
abcdefghijklmnopqrstu
In the code, set the text of the label using setText:
[self.myLabel setText:#"MyLabelText"];
This did it for me. Your situation may be a little different in that I wasn't changing the width of the superview and it sounds like you might be. But if the width constraint is set on the label then I would expect this to work in your case, too.
I had a similar question about label resizing, and the answer that I found that was useful to me is here: UILabel Auto Size Label to Fit Text. This is a good starting source for code on how to resize your label programmatically.
I would recommend that you also add a horizontal trailing auto layout constraint from the label to the edge of the superview. Do that and you can then get rid of your current width constraint.
AutoLayout facilitate you for orientation purpose. I don think it will give you automatic expansion. You have to define label with width and height completely, otherwise you will see dots at the end of label. So you may use UITextView expanding it all over the screen. And set textView.backgroundcolot = clearColor.

Using autolayout to manage height of UILabel within a subview

Using Xcode 5, interface builder and developing for iOS 7.
Within my content view I have 2 additional sub views, one on top of another. Within the upper subview I have a UILabel. I would like for that UILabel to expand in height when the content exceeds the first line, but I can't seem to get the height increase of the UILabel to increase the height of the subview, thus pushing the bottom subview down the main content view.
Additionally, I would assume the content view would need some sort of a constraint that reflects the overall height of the two subviews?
Perhaps this the question has already been answered somewhere, but I've searched everywhere and can't seem to come up with a solution.
Any help would be hugely appreciated! Thanks in advance.
There is a couple of steps that you have to do to achieve this using autolayout.
Set layout constrains for the label.
Set height constraint with low priority.
Set numberOfLines to 0 to allow multiline text.
Set preferredMaxLayoutWidth for the label.
The preferredMaxLayoutWidth is used by label to calculate its height.
This property affects the size of the label when layout constraints
are applied to it. During layout, if the text extends beyond the width
specified by this property, the additional text is flowed to one or
more new lines, thereby increasing the height of the label.
Also, have a look here.

resize superview after subviews change dynamically using autolayout

I cant for the love of god the the hang of this resizing superview.
I have a UIView *superview with 4 UILabels. 2 function as header for the 2 others.
The content in all 4 are dynamic coming from database.
SizeToFit vs SizeThatFits:(CGSize) vs UIView systemLayoutSizeFittingSize:, passing either UILayoutFittingCompressedSize or UILayoutFittingExpandedSize.
I use autolayout programatically and have set the superview height to be equal or greater to a dummy number.
where and how do I use these SizeToFit vs sizeThatFits:(CGSize) vs UIView systemLayoutSizeFittingSize:, passing either UILayoutFittingCompressedSize or UILayoutFittingExpandedSize. I have read a lot of tips here on stack but ended up with nothing.
DO I need to recalculate the constraints for the superview somewhere specific. Maby setting the height to be ´#property` in its controller class and remove and readd it?
Atm I have tried to put everything everywhere and then some. Still I get the same size end result with the dummy height and text floating outside. Even after setting clipsToBound on subview.
I am scratching my hair of.. help
If you're using Auto Layout, here's what you need to do:
Make sure you aren't adding fixed width and/or height constraints to any of your subviews (depending on which dimension(s) you want to dynamically size). The idea is to let the intrinsic content size of each subview determine the subview's height. UILabels come with 4 automatic implicit constraints which will (with less than Required priority) attempt to keep the label's frame at the exact size required to fit all the text inside.
Make sure that the edges of each label are connected rigidly (with Required priority constraints) to the edges of each other and their superview. You want to make sure that if you imagine one of the labels growing in size, this would force the other labels to make room for it and most importantly force the superview to expand as well.
Only add constraints to the superview to set its position, not size (at least, not for the dimension(s) you want it to size dynamically). Remember that if you set the internal constraints up correctly, its size will be determined by the sizes of all the subviews, since its edges are connected to theirs in some fashion.
That's it. You don't need to call sizeToFit or systemLayoutSizeFittingSize: to get this to work, just load your views and set the text and that should be it. The system layout engine will do the calculations for you to solve your constraints. (If anything, you might need to call setNeedsLayout on the superview...but this shouldn't be required.)
Use container views
In the following example I have a 30x30 image, and the UILabel is smaller than the containing view with the placeholder text. I needed the containing view to be at least as big as the image, but it needed to grow to contain multi-line text.
In visual format the inner container looks like this:
H:|-(15.0)-[image(30.0)]-(15.0)-[label]-(15.0)-|
V:|[image(30.0)]|
V:|[label(>=30.0)]|
Then, set the containing view to match the height of the label. Now the containing view will ride the size of the label.
As #smileyborg pointed out in his answer, connecting the content rigidly to the superview informs the layout engine that the simple container view should cause it to grow.
Yellow alignment rectangles
If you want the yellow alignment rectangles add -UIViewShowAlignmentRects YES in your scheme's list of run arguments.
This almost follows #smileyborg answer and comes with a concrete example.
Won't describe all constraints, but those related to the calculation of the height of UI objects.
[Label] Labels must not have a fixed height constraint, in this case, AutoLayout won't resize labels to fit the text, so setting edge constraints is the key. (green arrows)
[Subview] Steps 1 and 3 are very easy to follow, but this step can be misunderstood. As in the case with labels, subviews must not have height constraint set. All subviews must have top constraint set, ignoring bottom constraint, which can make you think will trigger unsatisfied constraint exception at runtime, but it won't if you set bottom constraint for the last subview. Missing to do so will blow the layout. (red arrows)
[Superview] Set all constraints the way you need, but pay big attention to the
height constraint. Assign it a random value, but make it optional, AutoLayout will set the height exactly to fit the subviews. (blue arrows)
This works perfectly, there is no need to call any additional system-layout update methods.
This was made dramatically easier with the introduction of Stack Views in iOS 9. Use a stack view inside your view to contain all your content that resizes, and then simply call
view.setNeedsUpdateConstraints()
view.updateConstraintsIfNeeded()
view.setNeedsLayout()
view.layoutIfNeeded()
after changing your content. Then you can get your new size by calling
view.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
if you ever need to calculate the exact size required for a view.

UILabel sizeToFit doesn't work with autolayout ios6

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.
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:
Set layout constrains for label
Set height constraint with low priority. It should be lower than ContentCompressionResistancePriority
Set numberOfLines = 0
Set ContentHuggingPriority higher than label's height priority
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
Set up four constraints. The height constraint is mandatory.
Then go to the label's attributes inspector and set number of lines to 0.
Go to the label's size inspector and increase vertical ContentHuggingPriority and vertical ContentCompressionResistancePriority.
Select and edit height constraint.
And decrease height constraint priority.
Enjoy. :)
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.
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:
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:
Now, let's select the UILabel in Interface Builder, and take a look at the default settings in the Size inspector:
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?
Now we can see the magic start to happen. Let's try in the sim:
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!
Noticed in IOS7 sizeToFit wasn't working also - perhaps the solution may help you too
[textView sizeToFit];
[textView layoutIfNeeded];
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
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.
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];
}
I added UILabel programmatically and in my case that was enough:
label.translatesAutoresizingMaskIntoConstraints = false
label.setContentCompressionResistancePriority(UILayoutPriorityRequired, forAxis: .Vertical)
label.numberOfLines = 0
i have solved with xCode6 putting "Preferred Width" to Automatic and pin the label top, leading and trailing
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));
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.
A solution that worked for me; If your UILabel has a fixed width, change the constraint from constant = to constant <= in your interface file
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.

Resources