Vertical list of UILabels and NSLayoutConstraint - ios

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.

Related

add subviews generated by autoayout to a parent view

This is my very first program in autolayout.
Basic problem: i am not able to add subviews(a uibutton and a uilabel) to a superview(a containerview).Subviews are just out of bond of superview or say not clipped.
I have added commented in detail to be better understanding of code.
What i want:
i dont care whereever containerview is but i want both subviews to be add in containerview with 0 padding from all sides.
- (void)viewDidLoad {
[super viewDidLoad];
**//create a uibutton with dynamic text(MAX_WIDTH=500, height = 60) and uilabel of fixed size(60, 60).Done
//create pin of fixed 2 pixes between UIButton and UILabel.Done
//put above created views in container view, it will max to 562 width and fix 60 height, so UIButton and UIlabel should fill container view with no top, bottom, left and right.Fail**
//this will be containing my button and my label
UIView *superview = self.view;
UIView *containerView = [UIView new];
[containerView setBackgroundColor:[UIColor blackColor]];
[containerView setTranslatesAutoresizingMaskIntoConstraints:NO];
[superview addSubview:containerView];
//this will be containing my button and my label
UILabel *mylabel = [[UILabel alloc]init];
[mylabel setBackgroundColor:[UIColor redColor]];
[mylabel setTranslatesAutoresizingMaskIntoConstraints:NO];
mylabel.text = #"MyLabel";
UIButton *mybutton = [UIButton
buttonWithType:UIButtonTypeRoundedRect];
[mybutton setTitle:#"My Button ye ye yey yeyeyye yeyey"
forState:UIControlStateNormal];
[mybutton setTranslatesAutoresizingMaskIntoConstraints:NO];
[mybutton setBackgroundColor:[UIColor greenColor]];
[containerView addSubview:mylabel];
[containerView addSubview:mybutton];
NSDictionary * views = NSDictionaryOfVariableBindings(mybutton,mylabel);
//create pin of fixed 2 pixes between UIButton and UILabel.Done
NSArray * horizontalConstraintsforbuttons = [NSLayoutConstraint constraintsWithVisualFormat:#"H:[mybutton(<=500)]-2-[mylabel(60)]" options:0 metrics:nil views:views];
NSArray * heightConstraintforbutton = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[mybutton(==60)]" options:0 metrics:nil views:views];
NSArray * heightConstraintforLabel = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[mylabel(==60)]" options:0 metrics:nil views:views];
[containerView addConstraints:horizontalConstraintsforbuttons];
[containerView addConstraints:heightConstraintforbutton];
[containerView addConstraints:heightConstraintforLabel];
//container view specific constraints//**it must be ideally <=562, but then this container view disappears, please hep to fix**
NSArray *widthConstraintForConstraint = [NSLayoutConstraint constraintsWithVisualFormat:#"H:[containerView(==560)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(containerView)];
NSArray *heightConstraint = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[containerView(==60)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(containerView)];
[superview addConstraints:widthConstraintForConstraint];
[superview addConstraints:heightConstraint];
[superview addConstraint:[NSLayoutConstraint constraintWithItem:containerView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:superview attribute:NSLayoutAttributeCenterX multiplier:1.0f constant:0.0]];
[superview addConstraint:[NSLayoutConstraint constraintWithItem:containerView attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:superview attribute:NSLayoutAttributeCenterY multiplier:1.0f constant:0.0]];
}
Any suggestion? :)
The VFL for each of your subviews is missing a relationship with the parent view. Autolayout is assuming your constraints should be in relation to the top-level view — what you've defined as self.view.
Here's where your problem is.
NSDictionary * views = NSDictionaryOfVariableBindings(mybutton,mylabel);
//create pin of fixed 2 pixes between UIButton and UILabel.Done
NSArray * horizontalConstraintsforbuttons = [NSLayoutConstraint constraintsWithVisualFormat:#"H:[mybutton(<=500)]-2-[mylabel(60)]" options:0 metrics:nil views:views];
NSArray * heightConstraintforbutton = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[mybutton(==60)]" options:0 metrics:nil views:views];
NSArray * heightConstraintforLabel = [NSLayoutConstraint constraintsWithVisualFormat
First, add your containerView to that dictionary so you can refer to it in VFL:
NSDictionary *views = NSDictionaryOfVariableBindings(mybutton,
mylabel,
containerView);
Then in your VFL, use the pipe operator (|) to tell autolayout to place your subviews in relation to their immediate parent.
NSArray * horizontalConstraintsforbuttons = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|[mybutton(<=500)]-2-[mylabel(60)]" options:0 metrics:nil views:views];
NSArray * heightConstraintforbutton = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[mybutton(==60)]|" options:0 metrics:nil views:views];
NSArray * heightConstraintforLabel = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|[mylabel(==60)]|" options:0 metrics:nil views:views];
I'm not entirely sure what you're trying to do with the horizontal placement of those views, but this should get you back on track. I recommend reading this post on VFL, too.
Edit
I sort of see what you're trying to do now. First, base your values at 1x when working with VFL and autolayout. As an example, a width of 560 is larger than the largest possible iPhone screen:
NSArray *widthConstraintForConstraint = [NSLayoutConstraint constraintsWithVisualFormat:#"H:[containerView(==560)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(containerView)];
Let's pretend you just wanted containerView to match the width of the device. That would look like this:
#"H:|[containerView]|"
Those pipe operators outside the containerView are saying that you want the leading (left side) and trailing (right side) space of containerView to be flush with the superview.
Alternatively, let's say you wanted your view to be slightly smaller than the width of the device (560/2).
#"H:[containerView(==280)]"
You're already horizontally centering containerView elsewhere, so it'll appear in the center of it's superview.
Let's then assume you want your red label to (actually) have a width of 60 and your green button to have a width less than or equal to 250 (500/2). That would look like this:
#"H:|[mybutton(<=250)]-2-[mylabel(==60)]|"
Since these are subviews of containerView (and we told that to autolayout earlier), the pipe operators are saying you want
the leading space (left side) of mybutton to be flush with containerView.
the trailing space (right side) of mylabel to be flush with containerView.
Since mylabel has a width of 60, mybutton will be narrower (thanks to <=) to satisfy constraints, depending on the width of containerView.

How to automatically change height of UILabel object and position of other elements below, based on the content of UILabel, with autolayout enabled

I've found some answers related to this topic, but nothing works for me. I've tried setPreferredMaxLayoutWidth:, setting number of lines to 0, setting height constraint
to XYZ, or equal or greater than XYZ... and all that in many different combinations. What could be possibly wrong? Any ideas?
Selected label is the one that needs to change the height based on content. Label below it, and possible other elements below should move down if the label has content that doesn't fit in 1 line. There are no constraint problems reported by IB.
Here's how I've just successfully done it:
I set numberOfLines on the label to 0, so it will grow and shrink as necessary.
I gave the label >= 0 left and right leading/trailing space constraints to the container margins, so it can grow to a maximum width.
I did not put any height constraint on the label. The height will therefore be determined by the content.
I made sure that no vertical constraints on anything below the label were limiting its downward growth.
In particular, bear in mind that if you set a constraint from anything to the bottom of the screen, you'll need to make sure that its priority (or the priority of another vertical constraint in the chain from the label to the bottom) is set to a lower priority than the vertical Content Compression Resistance Priority of the label. This will make sure that the growth of the label's content can overcome the other vertical constraints.
If you are using Auto Layout in code, setting the frame does not work. You have to create the constraint set required for the layout you want. For this case you would need to add height constraints for your UILabel.
Full tutorial on how to do is here : http://www.thinkandbuild.it/learn-to-love-auto-layout-programmatically/
if you want to try to take care of it in code then you can approach it like this. i've laid out something akin to what you have then after 5 seconds it will swap in a new vertical constraint to make one of the labels taller. hope it steers you in the right direction ... or at least a direction!
NSArray * vertConstraint;
UIImageView * imageView = [[UIImageView alloc] init];
UILabel * labelOne = [[UILabel alloc] init];
UILabel * labelTwo = [[UILabel alloc] init];
UILabel * labelThree = [[UILabel alloc] init];
imageView.backgroundColor = [UIColor grayColor];
labelOne.backgroundColor = [UIColor redColor];
labelTwo.backgroundColor = [UIColor blueColor];
labelThree.backgroundColor = [UIColor orangeColor];
[imageView setTranslatesAutoresizingMaskIntoConstraints: NO];
[labelOne setTranslatesAutoresizingMaskIntoConstraints: NO];
[labelTwo setTranslatesAutoresizingMaskIntoConstraints: NO];
[labelThree setTranslatesAutoresizingMaskIntoConstraints: NO];
[self.view addSubview:imageView];
[self.view addSubview:labelOne];
[self.view addSubview:labelTwo];
[self.view addSubview:labelThree];
id topGuide = self.topLayoutGuide;
id bottomGuide = self.bottomLayoutGuide;
NSDictionary * viewsDictionary = NSDictionaryOfVariableBindings(imageView, labelOne,labelTwo,labelThree,topGuide, bottomGuide);
// initial vertical constraints. will be swapped out after 5 seconds (See below
vertConstraint = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|[topGuide]-100-[imageView(==200)]-20-[labelOne(==20)]-20-[labelTwo(==20)]-20-[labelThree(==20)]-(>=5)-[bottomGuide]|" options:0 metrics: 0 views:viewsDictionary];
[self.view addConstraints:vertConstraint];
// horizontal constraints for all the elements
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(>=0)-[imageView(==200)]-(>=0)-|" options:0 metrics: 0 views:viewsDictionary]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(>=0)-[labelOne(==200)]-(>=0)-|" options:0 metrics: 0 views:viewsDictionary]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(>=0)-[labelTwo(==200)]-(>=0)-|" options:0 metrics: 0 views:viewsDictionary]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(>=0)-[labelThree(==200)]-(>=0)-|" options:0 metrics: 0 views:viewsDictionary]];
//additional constraints to center them
[self.view addConstraint:
[NSLayoutConstraint constraintWithItem:imageView
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterX
multiplier:1
constant:0]];
[self.view addConstraint:
[NSLayoutConstraint constraintWithItem:labelOne
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterX
multiplier:1
constant:0]];
[self.view addConstraint:
[NSLayoutConstraint constraintWithItem:labelTwo
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterX
multiplier:1
constant:0]];
[self.view addConstraint:
[NSLayoutConstraint constraintWithItem:labelThree
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterX
multiplier:1
constant:0]];
//delay 5 seconds then swap out vertical constraints
// in this case change the (==20) to (==40) for height of element
// you can edit that string more dynamically to fit your needs
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
NSArray * newVertConstraint = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|[topGuide]-100-[imageView(==200)]-20-[labelOne(==20)]-20-[labelTwo(==40)]-20-[labelThree(==20)]-(>=5)-[bottomGuide]|" options:0 metrics: 0 views:viewsDictionary];
[self.view removeConstraints:vertConstraint];
[self.view addConstraints:newVertConstraint];
[self.view setNeedsUpdateConstraints];
[UIView animateWithDuration:1.5 animations:^{
[self.view layoutIfNeeded];
}];
});

How to resize custom UITableView separators on landscape and prevent from disappearing

I've decided to programmatically create my own UITableView separator lines because I need fine control over displaying a separator above and/or below each individual UITableViewCell. My tableView has static cells, so I do not create the separators in cellForRowAtIndexPath. Instead, I have propertys for each cell and in viewDidLoad, I add a top and/or bottom separator as needed. It's working, until I rotate to landscape and then the separator line does not stretch to fill the screen - it of course remains the same width it was when created. I'm not seeing how I can automatically adjust them to fit the width of the screen.
I tried adding Auto Layout constraints (leading, trailing, top/bottom), but for some reason it's not working - the width does not change, but there are no error messages logged to indicate anything is wrong with the constraints. The separator lines also sometimes disappear upon scroll or rotate, and if I comment out the auto layout constraints then they do not disappear.
So how can I make my custom cell separators always stretch to fill the device width upon rotation, and how do I prevent them from disappearing?
If it would be easier/better to create my custom cell separators in a different way, I am willing to do that. I just don't know how this can be done aside from my approach when the cells are static. I considered creating the views in the Storyboard, and setting up the constraints visually, but would that not be the equivalent of what I'm doing programmatically? If they were dynamic cells I would do it in cellForRowAtIndexPath.
//In viewDidLoad:
[self addTopSeparatorForCell:self.myCell];
//Helper method
- (void)addTopSeparatorForCell:(UITableViewCell *)cell {
UIView *topSeparator = [[UIView alloc] initWithFrame:CGRectMake(15, 1, cell.contentView.frame.size.width, 0.5)];
//add CALayer to preserve line separator visibility when row is highlighted
CALayer *backgroundColorLayer = [CALayer layer];
backgroundColorLayer.frame = topSeparator.bounds;
backgroundColorLayer.backgroundColor = [UIColor colorWithWhite:204/255.0f alpha:1].CGColor;
[topSeparator.layer addSublayer:backgroundColorLayer];
[cell.contentView addSubview:topSeparator];
//add auto layout constraints
topSeparator.translatesAutoresizingMaskIntoConstraints = NO;
NSLayoutConstraint *cn = nil;
cn = [NSLayoutConstraint constraintWithItem:topSeparator
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:cell.contentView
attribute:NSLayoutAttributeLeading
multiplier:1.0
constant:15];
[cell.contentView addConstraint:cn];
cn = [NSLayoutConstraint constraintWithItem:topSeparator
attribute:NSLayoutAttributeTrailing
relatedBy:NSLayoutRelationEqual
toItem:cell.contentView
attribute:NSLayoutAttributeTrailing
multiplier:1.0
constant:0];
[cell.contentView addConstraint:cn];
cn = [NSLayoutConstraint constraintWithItem:topSeparator
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:cell.contentView
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:1];
[cell.contentView addConstraint:cn];
}
EDIT: Thanks to # user1966109, we've been able to solve the issue with the lines not extending to fill the width, and now they are preserved when highlighting a cell. But one issue still remains that I haven't been able to solve, since I'm not sure why it's occurring. The separator lines disappear after scrolling down the scrolling back up. It's related to the auto layout constraints though because a previous solution which had other issues did not exhibit this problem. Here's the current solution that causes the lines to disappear. I'd appreciate it if someone knows how to prevent this problem (and preserve the other issues already resolved).
[self.cell addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(15#750)-[myView]-(-47#750)-|" options:0 metrics:0 views:viewsDictionary]];
[self.cell addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[myView(2)]-(-2#750)-|" options:0 metrics:0 views:viewsDictionary]];
You should not mix initWithFrame and Auto Layout. You can have a good result with a few lines using Visual Format Language for Auto layout:
//#interface TableViewController ()
#property (weak, nonatomic) IBOutlet UITableViewCell *cell;
//#implementation TableViewController
- (void)viewDidLoad
{
[super viewDidLoad];
UIView *myView = [[UIView alloc] init];
myView.backgroundColor = [UIColor redColor];
[self.cell.contentView addSubview:myView];
myView.translatesAutoresizingMaskIntoConstraints = NO;
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(myView);
[self.cell.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[myView]|" options:0 metrics:0 views:viewsDictionary]];
[self.cell.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[myView(2)]" options:0 metrics:0 views:viewsDictionary]];
}
This handles rotation perfectly.
Edit!
Set the following constraints if using a accessory view:
//Set a negative value to the trailing space in order to display myView under the accessory view
//Those constraints work for both self.cell and self.cell.contentView (kind of odd)
[self.cell addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(15#750)-[myView]-(-47#750)-|" options:0 metrics:0 views:viewsDictionary]];
[self.cell addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[myView(2)]-(-2#750)-|" options:0 metrics:0 views:viewsDictionary]];
With the initial help of user1966109, I have figured out constraints that address all of the problems and are working well:
[cell addConstraint:[NSLayoutConstraint constraintWithItem:imageView
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:cell
attribute:NSLayoutAttributeLeading
multiplier:1.0
constant:indent]];
[cell addConstraint:[NSLayoutConstraint constraintWithItem:imageView
attribute:NSLayoutAttributeTrailing
relatedBy:NSLayoutRelationEqual
toItem:cell
attribute:NSLayoutAttributeTrailing
multiplier:1.0
constant:0.0]];
[cell addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[imageView(0.5)]" options:0 metrics:0 views:viewsDictionary]];

Auto Layout - make a view's dimensions dependent on another view's

Simple deal: I'd like to make a UIView's width half of its superview's width. Here's my code:
//Display a red rectangle:
UIView *redBox = [[UIView alloc] init];
[redBox setBackgroundColor:[UIColor redColor]];
[redBox setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.view addSubview:redBox];
//Make its height equal to its superview's,
//minus standard spacing on top and bottom:
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-[redBox]-|" options:0 metrics:nil views:#{#"redBox": redBox}];
[self.view addConstraints:constraints];
//Make its width half of its superviews's width (the problem):
NSLayoutConstraint *constraint = [NSLayoutConstraint
constraintWithItem:redBox
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeWidth
multiplier:0.5
constant:0.0
];
[self.view addConstraint:constraint];
[self.view setBackgroundColor:[UIColor blueColor]];
This is what I get:
If I set multiplier to 1.0, then the view's width is half of its superview. But why is that?
The problem is probably that your red box view is under-constrained. You've given it a width, but nothing to tell it where it should position itself horizontally. Since it should be half the width of the superview, it could choose to position itself on the left half of the superview. Or the right half. Or the middle. You didn't specify, so the framework just gets to pick something.
In this case, it looks like it's choosing to align the center of the red box with the left edge of the superview. This seems like an odd choice, but again, you didn't specify anything. It can pick whatever it wants.
Add another constraint that will position the view horizontally. That should fix the problem.

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