How to programmatically add a UILabel with variable height and layout ios - ios

I have been searching for the whole day, but could not make it work.
I have a UIView with two labels, on above the other, on the left side, and one button on the right side. I added the constraints to let autolayout resize views accordingly. Everything was working perfectly when I had set one constraint for the height of the UIView (and no one constraint for the height of the two UILabel), but as the content of the lower UILabel will vary, I removed that constraint and set two constraints for the UILabel, one fixed for the upper UILabel and one with relation "greater than or equal" for the lower UILabel, and other one to fix the distance between the lower UILabel and the UIView.
It looks like auto layout is not capable of calculate the intrinsicContentSize of the lower UILabel, because it never increases its height above 10px, despite the content of the lower UILabel.
UIView *miView = [[UIView alloc] init];
UILabel *miLabel = [[UILabel alloc] init];
[miLabel setTranslatesAutoresizingMaskIntoConstraints:NO];
[miDetailsLabel setNumberOfLines:1];
[miDetailsLabel setText:#"Just one line."];
[miView addSubview:miLabel];
UILabel *miDetailsLabel = [[UILabel alloc] init];
[miDetailsLabel setTranslatesAutoresizingMaskIntoConstraints:NO];
[miDetailsLabel setNumberOfLines:0];
[miDetailsLabel setText:#"Enough text to show 3 lines on an IP4, except first of three UILabels on my test code, with no content"];
[miView addSubview:miDetailsLabel];
UIButton *miButton = [[UIButton alloc] init];
[miButton setTranslatesAutoresizingMaskIntoConstraints:NO];
[miView addSubview:miButton];
NSArray *constraint_inner_h = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(0)-[label]-(10)-[button(52)]-(0)-|" options:0 metrics:nil views:#{#"label":miLabel, #"button":miButton}];
NSArray *constraint_inner2_h = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(0)-[details]-(10)-[button]-(0)-|" options:0 metrics:nil views:#{#"details":miDetailsLabel, #"button":miButton}];
NSArray *constraint_label_v = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-(0)-[label(18)]-(2)-[details(>=10)]-(0)-|" options:0 metrics:nil views:#{#"label":miLabel, #"details":miDetailsLabel}];
NSArray *constraint_button_v = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[button(22)]" options:0 metrics:nil views:#{#"button":miButton}];
[miView addConstraint:[NSLayoutConstraint constraintWithItem:miButton attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:miView attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0]];
[miView addConstraints:constraint_inner_h];
[miView addConstraints:constraint_inner2_h];
[miView addConstraints:constraint_label_v];
[miView addConstraints:constraint_button_v];
I ve put a reduced version of the code.
any ideas of what I am missing?
Thanks
UPDATE : Thanks to Matt advice, I've tried this solution to set the proper value to preferredMawLayoutWidth:
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
NSArray *allKeys = [dictOpcionesTickets allKeys];
for (NSString *key in allKeys) {
NSArray *tmpArray = [dictOpcionesTickets objectForKey:key];
if (![[tmpArray objectAtIndex:0] isEqual:#""]) {
UILabel *tmpLabel = [tmpArray objectAtIndex:3];
tmpLabel.preferredMaxLayoutWidth = tmpLabel.frame.size.width;
NSLog(#"width: %f",tmpLabel.frame.size.width);
}
}
[self.view layoutIfNeeded];
}
To explain the code, as I said, I'm doing it dynamically, so I've created a dictionary with an array with the references to my UILabel (among other interesting information). When I run the code, I get the next log (with the code parsing 3 UILabel, first label with no content):
2014-12-28 20:40:42.898 myapp[5419:60b] width: 0.000000
2014-12-28 20:40:42.912 myapp[5419:60b] width: 0.000000
2014-12-28 20:40:43.078 myapp[5419:60b] width: 229.000000
2014-12-28 20:40:43.080 myapp[5419:60b] width: 229.000000
2014-12-28 20:40:43.326 myapp[5419:60b] width: 229.000000
2014-12-28 20:40:43.327 myapp[5419:60b] width: 229.000000
But I'm still getting the same result...the UIView is still showing a height equals to the minimum height set by the constraints, not showing the content of the UILabel.
UPDATE 2 Still fooling around.
I've tested my initial code but simplified on a fresh project in xcode 6, running on an actual iPhone 4 with iOS7, and it worked perfectly without setting preferredMaxLayoutWidth or subclassing UILabel, and even without calling layoutIfNeeded on parent view. But when it comes to the real project (I think it was originally builded in xcode 4 or 5) it does not work:
- (void)addLabelsDinamically {
self.labelFixed.text = #"Below this IB label goes others added dinamically...";
UIButton *miBoton = [[UIButton alloc] init];
miBoton.translatesAutoresizingMaskIntoConstraints = NO;
[miBoton setTitle:#"Hola" forState:UIControlStateNormal];
[self.viewLabels addSubview:miBoton];
[self.viewLabels addConstraint:[NSLayoutConstraint constraintWithItem:miBoton attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.viewLabels attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0]];
[self.viewLabels addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[boton(22)]" options:0 metrics:nil views:#{#"boton":miBoton}]];
[self.viewLabels addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:[boton(40)]" options:0 metrics:nil views:#{#"boton":miBoton}]];
UILabel *miLabel = [[UILabel alloc] init];
miLabel.translatesAutoresizingMaskIntoConstraints = NO;
miLabel.backgroundColor = [UIColor redColor];
miLabel.numberOfLines = 0;
miLabel.text = #"ñkhnaermgñlkafmbñlkadnlñejtnhalrmvlkamnñnañorenoetñnngñsdbmnñgwn";
[self.viewLabels addSubview:miLabel];
[self.viewLabels addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(8)-[label]-(10)-[boton]-(8)-|" options:0 metrics:nil views:#{#"label":miLabel,#"boton":miBoton}]];
[self.viewLabels addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[previous]-(8)-[label]" options:0 metrics:nil views:#{#"label":miLabel,#"previous":self.labelFixed}]];
UIView *pre = miLabel;
miLabel = [[UILabel alloc] init];
miLabel.translatesAutoresizingMaskIntoConstraints = NO;
miLabel.backgroundColor = [UIColor greenColor];
miLabel.numberOfLines = 0;
miLabel.text = #"ñkhnaermgñlkafmbñlkadnlñejtnhalrmvlkamnñnañorenoetñnngñsdbmnñgwn";
[self.viewLabels addSubview:miLabel];
[self.viewLabels addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(8)-[label]-(10)-[boton]-(8)-|" options:0 metrics:nil views:#{#"label":miLabel,#"boton":miBoton}]];
[self.viewLabels addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[previous]-(8)-[label]" options:0 metrics:nil views:#{#"label":miLabel,#"previous":pre}]];
}
Ah, on the same screen I have one UILabel added on IB, with numberOfLines set to '0', vertical constraint "greater or equal than" and horizontal constraint set in a similar way (but both constraints set on IB)...and it works perfectly...this is driving me nuts!! any help???

Well, finally I've found the error, as I stated in the "UPDATE 2", it was very strange that it was working on an UILabel added via IB, but not with those added dynamically.
So, I looked again to my screen on IB, and found that I have an height constraint with "equal" relation, when I changed it to "greater or equal than" it all started to work!!!! Why I did not notice that the constraint was not "greater or equal than"? Because the view with the constraint was getting larger and larger as I was adding views with labels inside. Is that a bug?
Well, to summarize if someone find this useful. "How to add UILabels multi-line which adapts to its content?"
In iOS 6 versions it was a bug with "preferredMaxLayoutWidth", not getting the width once it was "layouted", and as a result of that programmers must set it via code (the best solution is doing it dynamically, an example of that I put it on my above question).
Fortunately on iOS7 and later, this has been solved, and you can rely only with constraints.
Adding via IB
Add the UILabel, and set "number of lines" to '0' (this will make the UILabel use as many lines as it needs).
Add the constraints, you need at least 3 constraints (pin to top or bottom, and both sides), the fourth will be height with a relation "greater or equal than".
Be sure than the view container has enough room, or also has a height constraint with a relation "greater or equal than".
Adding via code (just paste the code posted above):
UILabel *miLabel = [[UILabel alloc] init]; // Initialitze the UILabel
miLabel.translatesAutoresizingMaskIntoConstraints = NO; // Mandatory to disable old way of positioning
miLabel.backgroundColor = [UIColor redColor];
miLabel.numberOfLines = 0; // Mandatory to allow multiline behaviour
miLabel.text = #"ñkhnaergñsdbmnñgwn"; // big test to test
[self.viewLabels addSubview:miLabel]; // Add subview to the parent view
[self.viewLabels addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(10)-[label]-(10)-|" options:0 metrics:nil views:#{#"label":miLabel}]]; // horizontal constraint
[self.viewLabels addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-(10)-[label]" options:0 metrics:nil views:#{#"label":miLabel}]];
UIView *pre = miLabel;
// Adding a second label to test the separation
miLabel = [[UILabel alloc] init];
miLabel.translatesAutoresizingMaskIntoConstraints = NO;
miLabel.backgroundColor = [UIColor greenColor];
miLabel.numberOfLines = 0;
miLabel.text = #"ñkhnaermgñlkafmbnoetñnngñsdbmnñgwn";
[self.viewLabels addSubview:miLabel];
[self.viewLabels addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(10)-[label]-(10)-|" options:0 metrics:nil views:#{#"label":miLabel,#"boton":miBoton}]];
[self.viewLabels addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[previous]-(10)-[label]" options:0 metrics:nil views:#{#"label":miLabel,#"previous":pre}]];
Thanks to those who has tried to help me!! This site is amazing :)

This answer will work in a programmatic situation when you are added the text information dynamically and if this above is on a UICollectionViewCell or UITableViewCell.
In these situations you will need to update the layout and constraints on the contentView, then set the preferedWidth:
-(void) layoutSubviews
{
[super layoutSubviews];
[self.contentView setNeedsLayout];
[self.contentView layoutIfNeeded];
//locationName.preferredMaxLayoutWidth = locationName.frame.size.width;
//locationDetails.preferredMaxLayoutWidth = locationDetails.frame.size.width;
[super layoutSubviews];
}
This is similar to other answers where u are getting the new frame information and using that for the preferredMaxLayoutWidth then updating the view.
You will need constraints added to those views for the vertical as a start point and the UILabel need their numberOfLines = 0
On second thoughts setting he preferredMaxLayoutWidth in the layoutSubViews is poor solution if your width changes, as well as the height. Best to set this else where like the init for that cell. In some ways it might not be needed if you are constraining the width. This in my situation lead to the preferredMaxLayoutWidth changing each time that cell was set.

Related

How to create three views using autolayout, with one fix width height and other two growing height

I am trying to create a view setup vertically where i have one UIView (fixed width, height) at the top and two UILabels (fixed width, dynamic height) at the bottom. Padding all around the view (aView) is 5. Padding all around of _mylabel is 5. Padding on left and right of _yourLable is 5. _yourLable will grow as based on text, but when text content is too large, it will just stop to grow for maintain padding from superview of 5.
This is what i have tried:
- (void)viewDidLoad {
[super viewDidLoad];
UIView *superview = self.view;
UIView *aView = [UIView new];
[aView setBackgroundColor:[UIColor blackColor]];
[aView setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.view addSubview:aView];
_mylabel = [[UILabel alloc]init];
[_mylabel setNumberOfLines:0];
[_mylabel setBackgroundColor:[UIColor redColor]];
[_mylabel setTranslatesAutoresizingMaskIntoConstraints:NO];
_mylabel.text = #"i am trying to create a view setup vertically where i have one UIView(fix width, height) at top and other two UILables(fix width, dynamic height) at bottom respectively. Padding on allaround of view/lables is 5. this is what i have tried:";
[self.view addSubview:_mylabel];
_yourLable = [[UILabel alloc]init];
[_yourLable setNumberOfLines:0];
[_yourLable setBackgroundColor:[UIColor redColor]];
[_yourLable setTranslatesAutoresizingMaskIntoConstraints:NO];
_yourLable.text = #"i am trying to create a view setup vertically where i have one UIView(fix width, height) at top and other two UILables(fix width, dynamic height) at bottom respectively. Padding on allaround of view/lables is 5. this is what i have tried:";
[self.view addSubview:_yourLable];
NSDictionary * views = NSDictionaryOfVariableBindings(aView,_mylabel, _yourLable, superview);
NSArray * heightConstraintforLabel = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-5-[aView(==200)]-5-[_mylabel]-5-[_yourLable]-5-|" options:0 metrics:nil views:views];
NSArray * widthConstraintforView = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-5-[aView]-5-|" options:0 metrics:nil views:views];
NSArray * widthConstraintformylabel = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-5-[_mylabel]-5-|" options:0 metrics:nil views:views];
NSArray * widthConstraintforyourLable = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-5-[_yourLable]-5-|" options:0 metrics:nil views:views];
[superview addConstraints:heightConstraintforLabel];
[superview addConstraints:widthConstraintforView];
[superview addConstraints:widthConstraintformylabel];
[superview addConstraints:widthConstraintforyourLable];
}
and
-(void)viewWillLayoutSubviews{
[super viewWillLayoutSubviews];
// Your layout logic here
CGFloat availableLabelWidth = _mylabel.frame.size.width;
_mylabel.preferredMaxLayoutWidth = availableLabelWidth;
availableLabelWidth = _yourLable.frame.size.width;
_yourLable.preferredMaxLayoutWidth = availableLabelWidth;
}
This is what i am getting, without warnings:
I want both labels to resize based on exact text height.
I want last red label to grow as per text written in it, but never go beyond bottom space of 5. That is it should grow but maintain bottom padding of 5.
I have tried various combination with vertical content compression for labels..., but not got exact solution.
Help :)
Just in case you have not set the priorities, use
[_mylabel setContentCompressionResistancePriority:751 forAxis:UILayoutConstraintAxisVertical];
[_mylabel setContentHuggingPriority:252 forAxis:UILayoutConstraintAxisVertical];
You don't need to set any priorities for _yourLable.
And you don't need to set the preferredMaxLayoutWidth for any of the labels in viewWillLayoutSubviews, hence you don't need to override viewWillLayoutSubviews. You can comment the whole method.
Verified on iOS 7 and iOS 9 devices.
Simulator screenshot looks like this,

ios how to setup a scrollview with autolayout?

i'm tying to programmatically create a scrollView that will show a description.
my hierarchy is probably going to be like this:
->root View
--->scroll View
----->content View
------->Label1
------->Label2
------->Label3
here is an image that shows the structure of the app:
this is the code so far, i get what i want except that now i need to include all this inside a scrollView (that will scroll only vertically).
i tried many different things but i still don't get how to do it.
UIView *contentView = [UIView autolayoutView];
contentView.backgroundColor = [UIColor greenColor];
[self.view addSubview:contentView];
NSDictionary *views2 = NSDictionaryOfVariableBindings(contentView);
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|[contentView]|" options:0 metrics:0 views:views2]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[contentView]" options:0 metrics:0 views:views2]];
UILabel *one = [UILabel autolayoutView];
UILabel *two = [UILabel autolayoutView];
UILabel *three = [UILabel autolayoutView];
for(UILabel *label in #[one,two,three]){
label.backgroundColor = [UIColor redColor];
label.text = #"fjeif\njeif\noesjf\nfjeif\njeif\noesjf\nfjeif\njeif\noesjf\nosfj";
label.numberOfLines = 0;
[contentView addSubview:label];
}
NSDictionary *views3 = NSDictionaryOfVariableBindings(one,two,three);
[contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|-[one]-|" options:0 metrics:0 views:views3]];
[contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-[one(two)]-[two(three)]-[three]-|" options:NSLayoutFormatAlignAllLeft | NSLayoutFormatAlignAllCenterX metrics:0 views:views3]];
thanks, sorry if i didn't describe my problem well enough, i'm in a hurry. looking at the image should be enough to understand though.
I had the same problem though I set all constraints from XIB. You can do like this.
Add leading edge, trailing edge, top space and bottom space constraints to labels with respect to container view. Then you can add these same constraints to container view with respect to scrollView. Do the same exercise for scrollView.
You just need to add one more constraint (Horizontally center in container view constraint)to container view.
Hope this will help you.

Vertical list of UILabels and NSLayoutConstraint

I have a vertical list of UILabels:
I want to be able to have all the labels line up with the ":" on the right side and keep the spacing to the left side of the superView (createDate label stay put, and the name and year labels would shift to the right).
Code:
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[nameLabel]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(nameLabel)]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[headerView]-[nameLabel]-[createDateLabel]-[yearLabel]" options:NSLayoutFormatAlignAllLeading metrics:nil views:NSDictionaryOfVariableBindings(headerView, nameLabel, createDateLabel, yearLabel)]];
EDIT:
Ok, after implementing some suggestions:
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-(52)-[nameLabel]-[createDateLabel]-[yearLabel]" options:NSLayoutFormatAlignAllTrailing metrics:nil views:NSDictionaryOfVariableBindings(headerView, nameLabel, createDateLabel, yearLabel)]];
gives gets them all right aligned:
I would prefer to keep it pinned to the headerView so if that view changes height, I won't need to recode the pin space. Also, if I pin to headerView, it causes the labels to shift all the way to the right:
So that might just be a losing battle.
I still need to figure out how to pin them to the left and keep the ":" lined up. Right now, I pin createDateLabel because when I'm visually looking at it, I can see its the widest. Is there way I can get it to know which label will be the widest?
You can do this by :
Align trailing edges not leading
Pin leading space from superview to be >= [some const value]. This will make the labels have at least the given spacing from the left edge.
Pin the vertical spacing as you are
If you know which label will be the longest, you can also just pin that elements leading space to superview to your constant.
All of these constraints can be made in interface builder too, so that makes your life slightly easier
How about adding individual constraints to shorter labels:
- (void)viewDidLoad
{
[super viewDidLoad];
UILabel *createDateLabel = [[UILabel alloc] init];
createDateLabel.text = #"Created date:";
createDateLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:createDateLabel];
UILabel *yearLabel = [[UILabel alloc] init];
yearLabel.text = #"Year:";
yearLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:yearLabel];
UILabel *nameLabel = [[UILabel alloc] init];
nameLabel.text = #"Name:";
nameLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:nameLabel];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[createDateLabel]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(createDateLabel)]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-40-[nameLabel]-[createDateLabel]-[yearLabel]" options:NSLayoutFormatAlignAllTrailing metrics:nil views:NSDictionaryOfVariableBindings(nameLabel, createDateLabel, yearLabel)]];
// skip these
// [self.view addConstraint:[NSLayoutConstraint constraintWithItem:nameLabel attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:createDateLabel attribute:NSLayoutAttributeRight multiplier:1.0 constant:0.0]];
// [self.view addConstraint:[NSLayoutConstraint constraintWithItem:yearLabel attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:createDateLabel attribute:NSLayoutAttributeRight multiplier:1.0 constant:0.0]];
}
and the output is this:
I agree it is easier to achieve it in IB, but if you are forced to do it in code..
Good reading is this: Creating Individual Layout Constraints
Make all the labels the same width (set a definite width constraint on all of them) and make their text alignment all be right-aligned.
Here's my rendering. No code - this was set up entirely using constraints in Interface Builder. The longest one ("Create Date:") is adopting its natural width; the others have the same width. All contain right-aligned text, obviously. Other needed constraints are obvious.

Resizing UITextView Without Losing Constraints

I'm trying to figure out how to resize UITextView (and everything actually) without losing constraints. Basically, I'm trying to layout a page where most components can have variable sizes (like description). I tried doing it with a simple use case where I have a UITextView and a UIButton underneath. I want to make sure that the position of the button is relative to the bottom of the UITextView.
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
CGRect frame = self.textView.frame;
int height = self.textView.contentSize.height;
frame.size.height = height;
self.textView.frame = frame;
}
What I ended up with is UITextView overlapping with UIButton. After doing a bit of research, it seems that if I replace the frame, all constraints are gone also. I tried copying the constraints over, but of course the pointer is still pointing at the old frame so that didn't help at all.
Is there a good way to solve a very dynamically laid out page? I'm trying to at least use interface builder rather than code everything.
EDITED
I tried updating the constraint as suggested, but that didn't actually resize the UITextView. Did I do it incorrectly? When I get the constant again, it's updated, but the height isn't changed visually. I did simplify my code by adding an IBOutlet for the constraint. Still no luck however.
int height = self.textView.contentSize.height;
self.textViewHeightConstraint.constant = height;
EDITED 2
I figured it out now. I had an extra constraint for the bottom and that was stopping me from actually resizing the UITextView.
The issue is how you've defined your button's top constraint. If it's to the label, when you adjust the label's height constraint, the button will move. For example, if doing it programmatically:
UILabel *label = [[UILabel alloc] init];
label.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:label];
label.text = #"Hello world";
UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[button setTitle:#"Submit" forState:UIControlStateNormal];
button.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:button];
NSDictionary *views = NSDictionaryOfVariableBindings(label, button);
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[label]" options:0 metrics:nil views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[button]" options:0 metrics:nil views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-[label]-[button]" options:0 metrics:nil views:views]];
NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:label attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:20];
[label addConstraint:heightConstraint];
Then, if you change the label's height constraint, the button will move:
heightConstraint.constant = 100;
[UIView animateWithDuration:1.0 animations:^{
[self.view layoutIfNeeded];
}];
If you've defined your UI in Interface Builder, select the button and check the top constraint of the button and make sure it's to the label, not the superview:
But, again, if the button's top constraint is to the label, when the label's height constraint changes, the button will move.

UIView not obeying autolayout constraints in UIScrollView

I am adding a UIView to a UIScrollView and constraining it such that it fills the horizontal space, except for some margins. My visual constraint looks like this:
#"|-16-[theLineView]-16-|"
I have made the view one pixel high so it will appear as a line, and placed it between two text labels:
#"V:[someOtherStuff]-[aTextLabel]-[theLineView]-[anotherLabel]"
However, I am finding that the width of the line is only expanding as far as the width of the longest label above/below it.
Why would this be?
P.S I have read this http://developer.apple.com/library/ios/#technotes/tn2154/_index.html
Code
Here is the entirety of the view controller code from a test project that exhibits this issue on the iPad sim.
- (void)viewDidLoad
{
[super viewDidLoad];
self.scrollView = [[UIScrollView alloc] init];
self.scrollView.translatesAutoresizingMaskIntoConstraints = NO;
self.scrollView.backgroundColor = [UIColor greenColor];
[self.view addSubview:self.scrollView];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|[scrollView]|"
options:0
metrics:0
views:#{#"scrollView":self.scrollView}]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[scrollView]|"
options:0
metrics:0
views:#{#"scrollView":self.scrollView}]];
self.line1 = [[UIView alloc] init];
self.line2 = [[UIView alloc] init];
self.label1 = [[UILabel alloc] init];
self.label2 = [[UILabel alloc] init];
self.label3 = [[UILabel alloc] init];
for (UILabel *label in #[self.label1, self.label2, self.label3])
{
label.text = #"I am a label and I am long enough that I can be multiline on an iphone but single on ipad";
}
for (UIView *view in #[self.line1, self.line2, self.label1, self.label2, self.label3])
{
view.translatesAutoresizingMaskIntoConstraints = NO;
view.backgroundColor = [UIColor redColor];
[self.scrollView addSubview:view];
}
//horizontal layout - all views/labels should fill the horizontal space expect for margin
for (UIView *view in #[self.line1, self.line2, self.label1, self.label2, self.label3])
{
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"|-16-[view]-16-|"
options:0
metrics:0
views:#{#"view":view}];
[self.scrollView addConstraints:constraints];
}
//vertical layout - stack em up
[self.scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-[lab1]-[line1(==1)]-[lab2]-[line2(==1)]-[lab3]-|"
options:0
metrics:0
views:#{#"lab1":self.label1, #"line1":self.line1, #"lab2":self.label2, #"line2":self.line2, #"lab3":self.label3}]];
}
UIScrollView automatically shrinks to fit the views inside it. You need to set the width absolutely somewhere.
Recommended tactic:
Completely fixiate the scrollview inside its parent-view using constraints (leading, trailing, top, bottom).
Create a UIView inside the UIScrollView and put everything you need inside it.
Set the constraints so that the UIView will act as a content-view (this means it is big enough to include all elements). Use intrinsic content-size, shrink-resistance and chaining of elements with constraints a lot. The resulting layout must be well-defined and unique (this means if you were to remove all constraints to the outside, the layout would still work).
Connect the bounds of the UIView with their superview (which is the actual content-view of the UIScrollView, NOT the UIScrollView!).
If you do this in interface-builder (it is possible), you need to re-check your constraints every time you touch something in that scene. And by touch I mean "select" not only "modify".
Found a working solution that should work for your use case, too. See here.
Expanding on number 4 of Patric Schenke's answer; because the content size of scrollView is fluid, pinning an internal view to its edges just doesn't work for determining the width of the view. Your left side pin will work, but both won't. Calculating the width of your view based on the next level container up is the way to go. As long as your self.scrollView is pinned flush to its container(which I call containerView), this line of code will accomplish what you want. Add this line to your for loop for horizontal constraints:
// Pin view's width to match the scrollView container's width
// -32 constant offset for margins
[containerView addConstraint:
[NSLayoutConstraint constraintWithItem:view
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:containerView
attribute:NSLayoutAttributeWidth
multiplier:1.0f
constant:-32]];
I found a simple constraint-based way to accomplish this (I haven't tested the extent of the brittleness of this solution):
...#"H:|-16-[view]-16-|"... // (your original constraint)
[self.scrollView addConstraint:
[NSLayoutConstraint constraintWithItem:view
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.scrollView
attribute:NSLayoutAttributeCenterX
multiplier:1.0f
constant:0.0f]];
This stretches view all the way out to the other side of the view. This is probably not ideal for content that scrolls horizontally, but should work vertically.
I realize this is over a year later, but it's simpler for the single dimensional scrolling use case than patric.schenke's answers (which are good and more robust).

Resources