I have a user form where there are a bunch of Label and its corresponding text field or drop down or checkbox group arranged vertically on Storyboard. Something like this:
Label 1
Text Field 1
Label 2
Check Box Group
Label 3
Drop Down Field
Label 4
Text Field 4
Under normal condition, constraint set up between Labels is 60 vertically.
The problem is since Check Box Group is dynamically populated from the data retrieved from server, I don't know how many checkboxes will be there between Label 2 and Label 3 at StoryBoard design time. So there comes a need to programmatically change the constraint between Label 2 and Label 3.
Currently, what I did was set constraint 60 between them but check "Remove at build time" and programmatically set the constraint depending on how many checkboxes are there between there. Something like this:
[_superView addConstraint:[NSLayoutConstraint constraintWithItem:_Label3 attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:_Label2 attribute:NSLayoutAttributeBottom multiplier:1 constant:100]];
But for some reasons , this is not working as expected. Although Label3 is correctly placed after the last checkbox, I could only see half of the Label4 which has height constraint. I tried:
[superView updateConstraints];
[superView layoutIfNeeded];
But to no avail.
You can add vertical spacing constraint in storyboard, and add an nslayoutconstraint iboutlet var in view controller and connect the same to the constraint in storyboard.
Now the constraint.constant value can be changed programmatically to whatever value you want to instead of adding it.
Personally, I'd be inclined to not have any constraints between "Label 1", "Label 2", etc. I would instead create a view with container views that will contain the various fields between the labels:
I'd define vertical constraints like so (I'm assuming you'd do this in storyboard, like I did above, but I'm showing example VFL as that's a concise and precise way of describing a whole set of vertical constraints):
V:|-[label1]-[group1(0#250)]-[label2]-[group2(0#250)]-[label3]-[group3(0#250)]-[label4]-[group4(0#250)]-|"
Note, in my snapshot, I gave those container "group" views a non-zero height and a gray background, so you can see them, but hopefully this illustrates the idea. Have placeholder views that will contain the controls underneath the various labels and give that a zero height but a low (250) priority (or, like you did, have it omit them at run time).
Then you can programmatically add whatever controls you want as subviews of their respective group views as set their constraints accordingly, e.g.
V:|-[MoView]-[LarryView]-[CurlyView]-|
And because those have a higher prior than the zero height constraints of the group boxes, the resulting view will be resized accordingly, e.g.:
You can probably accomplish something very similar using UIStackView (if targeting iOS 9 and later), but if using simple constraints, this is how I'd tackle it.
That way, you get out of the business of coding magical spaces between "Label 1" and "Label 2", which will become invalid if, for example, you change fonts at some future date (or better, support the use of the user-provided font sizes specified within the Settings app).
Standard Tableview app. Autolayout. Each (cell) class has its own .xib file, no storyboard.
I have a segmented control in a tableview cell filling it (almost) completely, with all 4 edges bound with 6 points space to its container (=content view of the tableViewCell). The segments are made from images, not text.
I would expect when I call -[setImage: forSegmentAtIndex:] that the control changes its intrinsic content size to make enough space for the segment images - especially the view height.
I did set it's Content Hugging Priority to 251 (and the cell's hugging priority stays at default 250), so when I use narrow images the segmented control height should shrink (and the cell content view height also). And when I use larger images the segmented control height should grow...
I compute the correct height by measuring an invisible reference cell (dequeued myself in -[viewDidLoad] and kept in a viewController property),
asking Autolayout for -[systemLayoutSizeFittingSize:UILayoutFittingCompressedSize] for the reference cell's contentView. This works fine, I get different sizes to return in -[tableView heightForRowAtIndexPath] for different images. So AutoLayout computes the height to be used correctly.
But the real (visible) cell of the tableView doesn't change the height of the segmented control. It always stays at 29 points (which is the standard height for a segmented control with text labels and not images), no matter what cellHeight being used and what images set.
I tried to add another NSLayoutConstraint to the segmented control, where I just set it's height:
heightConstraint = [NSLayoutConstraint constraintWithItem:self
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationGreaterThanOrEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1
constant:newHeight];
and add that (once) to the segmented control. I got "Unable to simultaneously satisfy constraints" when I tried NSLayoutRelationEqual, that's why I used NSLayoutRelationGreaterThanOrEqual. (Probably computed the newHeight value wrong).
But it still doesn't work - 29 points and garbled images in the segments...
What am I doing wrong? Or is this an iOS bug with UISegmentedControl and AutoLayout?
Of course I could just revert to manual layout and just set the frame of the segmented control - but I'd like to use AutoLayout...
I found a solution:
implement
-(CGSize)intrinsicContentSize
{
CGSize size = CGSizeMake(288, 68);
return size;
}
on my segmentedControl subclass. Well, of course compute the height based on the actual image height instead of using 68, but this works...
Consider a view that contains three UILabels with vertical contraints defined as follows:
[self.view
addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:#"V:[label1]-2-[label2]-2-[label3]"
options:0
metrics:nil
views:views]];
This will product a layout with the labels stacked vertically w/ 2px padding between each label.
In my app, sometimes the text displayed on one or more of the labels is nil (or blank) which means the label frame ends up with height == 0. In this case, I want the 2px padding between the 0 height label to be collapsed.
When all labels have a text value, I want the layout to be:
label1 text
[2px]
label2 text
[2px]
label3 text
When label2 has a nil text value, I want the layout to be:
label1 text
[2px]
label3 text
In the latter case, label 2 is actually still there but has 0 height and its 2px padding has been collapsed.
Question: Is this possible? How would I define constraints to accomplish this?
EDIT
I realize it is possible to implement the padding (edge inset) in a subclass of UILabel and take the padding out of the constraints. I may go that route, just hoping that there is a way to define collapsing this padding in the constraint definition.
You can redefine your constraints when the values you are displaying in your labels changes.
If this is a view subclass with three string properties, in the setter of each property, you can call setNeedsUpdateConstraints.
Then, in your updateConstraints implementation, remove the constraints previously created (which you can store in a property if required, since the VFL method returns an array) and regenerate the appropriate ones.
If you are in a view controller which is handling the layout, there is an analagous method for updating the constraints on the view controller's view: updateViewConstraints.
Yes, there is a way to do this, and you should deal with this by modifying your constraints instead of messing with a UILabel subclass. However, it's kind of tricky to do using VFL as you are currently doing though, because what you need to do is hold a reference to the individual constraints between each label, and VFL returns an array of constraints (so you won't know which is which).
So, assuming you're not using VFL, you'll want to create the constraints between each pair of labels individually and store it in an ivar or property:
NSLayoutConstraint *label1To2Constraint = [NSLayoutConstraint constraintWith...];
NSLayoutConstraint *label2To3Constraint = [NSLayoutConstraint constraintWith...];
If you don't explicitly add constraints to set a fixed height on the labels (the VFL code you're using doesn't do this), then if the label has no text in it, it will shrink to zero height. So now all you need to do is after updating the text for the labels, do some checks on whether the labels have text or not and update the constant property of the constraint between each pair as needed:
BOOL hasLabel1Text = label1.text.length > 0;
BOOL hasLabel2Text = label2.text.length > 0;
BOOL hasLabel3Text = label3.text.length > 0;
label1To2Constraint.constant = (hasLabel1Text && hasLabel2Text) ? 2.0f : 0.0f;
label2To3Constraint.constant = (hasLabel2Text && hasLabel3Text) ? 2.0f : 0.0f;
Changing the constant of existing constraints can (and should) be done without removing and re-adding the constraint. It's very efficient. (Removing and re-adding constraints is much more taxing on the internal Auto Layout engine, and should be avoided as much as possible.)
In order to save you some pain, I'd like to point you towards the UIView+AutoLayout category I've created. I promise it will dramatically improve your experience with Auto Layout using the thinnest layer of third party code possible. You'll see that it's very easy to create the constraints you need for this scenario.
This works well for this kind of problem:
https://github.com/depth42/AutolayoutExtensions
I'm creating an iOS view that displays various static text elements. The xib looks like this:
It uses four labels for the title, timestamp, body, and footer. Every view is anchored to the sibling view above it vertically and anchored to the left/right of the parent view. All labels have a fixed height except the body which has a >= height and the number of lines set to 0 with "word wrap" as the line wrapping style. The parent view is a UIScrollView.
On the iPhone it looks like fine:
However on the iPad it looks like this:
Huh? Where is all that extra vertical space in the body label coming from? The xib and its view controller are identical between iPhone and iPad (there is no custom iPad code at the moment). I've found that the vertical space is directly related to how many line-wraps the label renders. If no lines wrap, no extra vertical space. If only a few lines wrap, there's a little extra vertical space. If nearly every line wraps, well, that's what it looks like.
First of all any ideas on why UILabel is behaving this way?
Second of all, if I can't make it stop doing this how can I work around it?
I've already tried a few things. If I call [bodyLabel sizeToFit] within -viewDidLayoutSubViews then it fixes the label but doesn't fix the layout of any of the sibling views (e.g. the Footer label is stuck way at the bottom of the screen instead of pulled up to just under the body). Any attempts to get the entire view to re-layout its children after calling sizeToFit is ignored. I've also tried sizing the UILabel by calculating height based on font, which results in the same behavior as -sizeToFit (albeit with more code).
Replacing the Body UILabel with a UITextView instead doesn't give me the weird vertical spacing issues but I need to calculate the height of the UITextView manually (using font calculations) and something about resizing the UITextView within the parent UIScrollView makes it so the UIScrollView simply refuses to scroll (as if it doesn't know its contents are too big for its bounds).
So at the moment I'm stuck. Even just an explanation of why UILabel behaves this way on the iPad layout would be helpful.
In case anyone else runs into this same issue using autolayout... I may have been able to solve the same issue by creating a constraint as Coche suggests, but I realized I had a preferredMaxLayoutWidth that was too small set on the uilabel. Once I set an accurate preferredMaxLayoutWidth (the actual width of the label) the spacing on top and bottom disappeared.
The main problem is that the method for auto resizing the text inside your Label is failing because in iPad your Label doesn't have a set width from the beginning, it is calculated on run time and that's the source of that mess. On iPhone, as your Label has a set width (on IB) there is no troubles.
There are two ways for solving the problem:
Having two storyboards : one for iPhone and one for iPad
Doing this will make that your Label knows its width since the beginning and it will just works as on iPhone.
Having just one Storyboard for both iPhone and iPad
You can go around the problem by calculating the size that best fits its text and with that result add a height constraint by code to the Label. For calculating the desiredSize you can calculate the width with this formula: Current View's width - (Leading space + Trailing Space). Here is my code
CGSize desiredSize = [_bodyLabel sizeThatFits:CGSizeMake(self.view.frame.size.width-40, 10)];
NSString *visualContraint = [NSString stringWithFormat:#"V:[_bodyLabel(%.0f)]",desiredSize.height];
[_bodyLabel addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:visualContraint
options:NSLayoutFormatDirectionLeadingToTrailing
metrics:nil
views:NSDictionaryOfVariableBindings(_bodyLabel)]];
objective-c
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.