Center views using *only* visual format? - ios

I'm trying to center 2 views with different height vertically.
UILabel *view1 = [[UILabel alloc] init];
view1.backgroundColor = [UIColor redColor];
view1.text = #"view1\nline2";
view1.textAlignment = NSTextAlignmentCenter;
view1.numberOfLines = 0;
UILabel *view2 = [[UILabel alloc] init];
view2.backgroundColor = [UIColor greenColor];
view2.text = #"view2\nline2";
view2.textAlignment = NSTextAlignmentCenter;
view2.numberOfLines = 0;
[self.view addSubview:view1];
[self.view addSubview:view2];
NSDictionary *views = #{#"view1": view1, #"view2" : view2};
[self.view setTranslatesAutoresizingMaskIntoConstraints:NO];
for (UIView *view in views.allValues) {
[view setTranslatesAutoresizingMaskIntoConstraints:NO];
}
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[view1]-[view2(==view1)]-|"
options:NSLayoutFormatAlignAllCenterY
metrics:nil views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-(>=20)-[view1(200)]-(>=20)-|"
options:NSLayoutFormatAlignAllCenterY
metrics:nil views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-(>=20)-[view2(100)]-(>=20)-|"
options:NSLayoutFormatAlignAllCenterY
metrics:nil views:views]];
This manages to make the centers of both aligned vertically, but they are at the bottom of the superview!
I want to center vertically not only relative to each other, also in the superview.
I added a constraint like this and it works:
[self.view addConstraint:
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterY
multiplier:1
constant:0]];
But I want to know, is this possible to achieve using only with visual format?

Yes, it's possible, but only by adding spacer views. So if you create a couple of views, lets call them spacer1 and spacer2, you can center view1 with this string,
#"V:|[spacer1][view1][spacer2(==spacer1)]|"
This assumes that view1 has a fixed height. I wouldn't recommend doing it this way though, I would just use the code you show at the bottom of your post.

What about calculating the Y in "metrics" ?
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-centerY-[view1(200)]-(>=20)-|"
options:NSLayoutFormatAlignAllCenterY
metrics:#{#"centerY": #(CGRectGetHeight(self.view.frame)/2.0 - 100)}
views:views]];

Related

AutoLayout programmatically doesn't work

I want to go into the depths of auto layout and so I want to do it programmatically in order to understand it properly.I know how to use it through storyboard pretty well.Now here is what I am doing(in viewDidAppear:)
-(void)scenario2{
PGView *viewA = [[PGView alloc]initWithFrame:CGRectMake(100, 100, 100, 100) andBackgroundColor:[UIColor blackColor]];
[self.view addSubview:viewA];
PGView *viewB = [[PGView alloc]initWithFrame:CGRectMake(200, 300, 100, 100) andBackgroundColor:[UIColor yellowColor]];
[self.view addSubview:viewB];
viewA.translatesAutoresizingMaskIntoConstraints = NO;
viewB.translatesAutoresizingMaskIntoConstraints = NO;
NSLayoutConstraint *constraint;
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[viewB(==100)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(viewB)]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:[viewB(==100)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(viewB)]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[viewA(==200)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(viewA)]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:[viewA(==200)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(viewA)]];
//problem statement
constraint = [NSLayoutConstraint constraintWithItem:viewA attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:viewB attribute:NSLayoutAttributeWidth multiplier: 3.0f constant: 0.0f];
[self.view addConstraint:constraint];
NSLog(#"%#",viewA);
NSLog(#"%#",viewB);
}
So here I have initialized two views with different colors.
I know I need to set the property. translatesAutoresizingMaskIntoConstraints to NO before applying any new constraints. So I have done that.
Now I am specifying heights and widths of both the views.So,the x_position and y_position for both the views are 0.I run the code and I get the expected result. Both views have the specified height and width at point (0,0).
Problem
Now when I am writing the statement "problem statement" to adjust the width of viewB according to the width of viewA,its not working.
Also, when I print views viewA and viewB,it prints the frames as (0,0,0,0).
Can anyone explain me this behavior?
Edit:Here are some modifications that I have made.I have checked the ambiguity of both the views using 'hasAmbiguousLayout' and its working fine now.Now what should be the next step if I want to resize viewB's width thrice to that of viewA's width?
-(void)scenario2{
PGView *viewA = [PGView new];
viewA.backgroundColor = [UIColor yellowColor];
[self.view addSubview:viewA];
PGView *viewB = [PGView new];
viewB.backgroundColor = [UIColor blackColor];
[self.view addSubview:viewB];
viewA.translatesAutoresizingMaskIntoConstraints = NO;
viewB.translatesAutoresizingMaskIntoConstraints = NO;
NSLayoutConstraint *constraint;
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-[viewB(==100)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(viewB)]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[viewB(==100)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(viewB)]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-[viewA(==200)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(viewA)]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[viewA(==200)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(viewA)]];
NSLog(#"%d",viewA.hasAmbiguousLayout);
NSLog(#"%d",viewB.hasAmbiguousLayout);
NSLog(#"%#",viewA);
NSLog(#"%#",viewB);
}
The problem is that you've explicitly set width constraints for A and B to be 200 and 100, respectively. So the additional constraint that specifies that one should have a width of three times the other one is unsatisfiable when combined with the other constraints.
Furthermore, the constraints are ambiguous, as you've defined width and height constraints, but haven't defined where the two frames should be. Yes, you've defined the frame for these two views, but that is ignored when you apply the constraints. You probably shouldn't define the frame values at all, as it's misleading and confusing. But you should set, for example, the leading and top constraints, or specify those in the VFL, or add centerX and centerY constraints. You just want some constraints to dictate where the views should be placed and there's lots of ways to specify that.
And the reason that you're not seeing reasonable frame values at the end is that you've defined the constraints, but they haven't been applied yet. You could examine them in viewDidLayoutSubviews, if you wanted.
You might do something like:
- (void)scenario2 {
PGView *viewA = [PGView new];
viewA.backgroundColor = [UIColor yellowColor];
[self.view addSubview:viewA];
PGView *viewB = [PGView new];
viewB.backgroundColor = [UIColor blackColor];
[self.view addSubview:viewB];
viewA.translatesAutoresizingMaskIntoConstraints = NO;
viewB.translatesAutoresizingMaskIntoConstraints = NO;
NSDictionary *views = NSDictionaryOfVariableBindings(viewA, viewB);
// note, I added `|-100-` to say it's 100 points from the top (and 100 tall)
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-100-[viewB(==100)]" options:0 metrics:nil views:views]];
// likewise, to say 200 points from the left (and 100 wide)
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-200-[viewB(==100)]" options:0 metrics:nil views:views]];
// again, 100 from the top (as well as 200 tall)
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-100-[viewA(==200)]" options:0 metrics:nil views:views]];
// and 100 from the left
// but I've removed width definition, as we'll use your multiplier constraint below
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-100-[viewA]" options:0 metrics:nil views:views]];
// now that we've removed the width constraint from the above,
// this should now work fine
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:viewA attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:viewB attribute:NSLayoutAttributeWidth multiplier: 3.0f constant: 0.0f];
[self.view addConstraint:constraint];
// no point in doing this, because constraints haven't yet been applied
//
// NSLog(#"%#",viewA);
// NSLog(#"%#",viewB);
}
// if you want to see where they are, you could do that here:
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
for (UIView *view in self.view.subviews) {
NSLog(#"%#", view);
}
NSLog(#"-----");
}

scrollView: how to create a pure auto layout scrollview with paging?

Can someone tell me what I did wrong here...
- pin the UIScrollView to its container view
- pin all subviews onto UIScrollView
After reading the Apple TechNote about it, I tried both hybrid method and pure auto layout method. The hybrid method using NIB works awful with paging, it looks like a big picture in a scrollview, rather than paged.
I then created the pure auto layout version in code, UIScrollView as subview of a UIView. This time the view stuck, and the UIImage is gigantic, like its full size:
//scroll view
if (self.scrollView == nil)
{
self.scrollView = [[UIScrollView alloc] initWithFrame:self.frame];
self.scrollView.translatesAutoresizingMaskIntoConstraints = NO;
[self.scrollView setClipsToBounds:NO];
[self.scrollView setPagingEnabled:YES];
[self addSubview: self.scrollView];
[self addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[scrollView(300)]|"
options:0 metrics:nil
views:#{#"scrollView":self.scrollView}]];
[self addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[scrollView(300)]|"
options:0 metrics:nil
views:#{#"scrollView":self.scrollView}]];
-(void) createContentView
{
for (int i=0; i<self.pageImages.count; i++) {
UILabel* topLabel = [[UILabel alloc] init];
topLabel.text = [NSString stringWithFormat:#"topLabel %d", i+1];
[topLabel sizeToFit];
[self.topLabelArray insertObject:topLabel atIndex:i];
[self.scrollView addSubview:topLabel];
topLabel.translatesAutoresizingMaskIntoConstraints = NO;
UILabel* bottomLabel = [[UILabel alloc] init];
bottomLabel.text = [NSString stringWithFormat:#"bottomLabel %d", i+1];
[bottomLabel sizeToFit];
[self.bottomLabelArray insertObject:bottomLabel atIndex:i];
[self.scrollView addSubview:bottomLabel];
bottomLabel.translatesAutoresizingMaskIntoConstraints = NO;
UIButton* button = [[UIButton alloc] init];
button.titleLabel.text = [NSString stringWithFormat:#"button %d", i+1];
[button sizeToFit];
[self.buttonArray insertObject:button atIndex:i];
[self.scrollView addSubview:button];
button.translatesAutoresizingMaskIntoConstraints = NO;
UIImageView* imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:[self.pageImages objectAtIndex:i]]];
imageView.frame = CGRectMake(0,0,200,200);
imageView.translatesAutoresizingMaskIntoConstraints = NO;
imageView.contentMode = UIViewContentModeScaleAspectFit;
[self.pageViews insertObject:imageView atIndex:i];
[self.scrollView addSubview:imageView];
NSDictionary* viewsDictionary = #{#"topLabel":topLabel,
#"bottomLabel":bottomLabel,
#"button": button,
#"imageView": imageView
};
[self.scrollView addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-(10)-[topLabel]-(10)-[imageView]-(10)-[bottomLabel]-(10)-|"
options:0 metrics:nil
views:viewsDictionary]];
[self.scrollView addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"V:[button]-(10)-|"
options:0 metrics:nil
views:viewsDictionary]];
if (i==0)
{
[self.scrollView addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(10)-[topLabel]"
options:0 metrics:nil
views:viewsDictionary]];
[self.scrollView addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(10)-[imageView]"
options:0 metrics:nil
views:viewsDictionary]];
[self.scrollView addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(10)-[bottomLabel]-(10)-[button]"
options:0 metrics:nil
views:viewsDictionary]];
}
else if (i == self.pageImages.count)
{
[self.scrollView addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"H:-(10)-[topLabel]-(10)-|"
options:0 metrics:nil
views:viewsDictionary]];
[self.scrollView addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"H:-(10)-[imageView]-(10)-|"
options:0 metrics:nil
views:viewsDictionary]];
[self.scrollView addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"H:-(10)-[bottomLabel]-(10)-[button]-(10)-|"
options:0 metrics:nil
views:viewsDictionary]];
}
else
{
[self.scrollView addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"H:[prevTopLabel]-(10)-[topLabel]"
options:0 metrics:nil
views:#{#"prevTopLabel": [self.topLabelArray objectAtIndex: i-1],
#"topLabel": topLabel
}]];
[self.scrollView addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"H:[prevImageView]-(10)-[imageView]"
options:0 metrics:nil
views:#{#"prevImageView": [self.pageViews objectAtIndex: i-1],
#"imageView": imageView
}]];
[self.scrollView addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"H:[prevButton]-(10)-[bottomLabel]-(10)-[button]"
options:0 metrics:nil
views:#{#"prevButton": [self.buttonArray objectAtIndex: i-1],
#"button":button,
#"bottomLabel": bottomLabel
}]];
}
[self.scrollView addConstraint:[NSLayoutConstraint
constraintWithItem:topLabel
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:imageView
attribute:NSLayoutAttributeCenterX
multiplier:1
constant:0.0]];
[self.scrollView addConstraint:[NSLayoutConstraint
constraintWithItem:bottomLabel
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:imageView
attribute:NSLayoutAttributeCenterX
multiplier:0.8
constant:0.0]];
// [self.scrollView addConstraint:[NSLayoutConstraint
//
Ok, there's a few things wrong here:
else if (i == self.pageImages.count)
This clause will never run, as your loop count is set to break when i == self.pageImages.count -1, here for (int i=0; i<self.pageImages.count; i++).
Next
[self.scrollView addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"H:-(10)-[topLabel]-(10)-|"
options:0 metrics:nil
views:viewsDictionary]];
[self.scrollView addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"H:-(10)-[imageView]-(10)-|"
options:0 metrics:nil
views:viewsDictionary]];
[self.scrollView addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"H:-(10)-[bottomLabel]-(10)-[button]-(10)-|"
options:0 metrics:nil
views:viewsDictionary]];
These will also produce invalid constraints, specifically #"H:-(10)-[bottomLabel].... is not pinning the furthest left constraint to anything. All visual format strings that connect views need to start or finish with a view. Either a subview ([subview]) or a superview (|). To fix this, you need to keep a reference to the previous pages label in the loop, and add that to the beginning of your VFL string. Something like this
#"H:[prevBottomLabel]-(10)-[bottomLabel]....
prevBottomLabel = bottomLabel;
// Continue loop
Next:
UIImageView* imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:[self.pageImages objectAtIndex:i]]];
imageView.frame = CGRectMake(0,0,200,200);
imageView.translatesAutoresizingMaskIntoConstraints = NO;
imageView.contentMode = UIViewContentModeScaleAspectFit;
This will not have the desired effect, as you can't manually set the frame, then switch on autolayout. Currently, the frame value is ignored, and the height and width are set by the intrinsic content size of the image view, which will be the height and width of the image. If you want to set the height and width, you need to do it with constraints, as so:
[imageView addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"H:[imageView(200)]"
options:0 metrics:nil
views:viewsDictionary]];
[imageView addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"V:[imageView(200)]"
options:0 metrics:nil
views:viewsDictionary]];
That should give you some pointers, there may be other mistakes in there too but fixing these is a good place to start.

How to do AutoLayout programmatically with Horizontal scrolling?

I'm trying to do Horizontal scrolling on iOS using AutoLayout programmatically. Here's the link to my github I'm trying to add another NewsSection to the next page but I'm not sure how to do it. Here's the code I'm working on.
- (void) setupNewsView
{
UIView *newsView = [[NewsSection alloc] initWithFrame:CGRectZero];
newsView.backgroundColor = [UIColor redColor];
UIView *anotherNewsView = [[NewsSection alloc] initWithFrame:CGRectZero];
anotherNewsView.backgroundColor = [UIColor blueColor];
self.scrollView.translatesAutoresizingMaskIntoConstraints = NO;
newsView.translatesAutoresizingMaskIntoConstraints = NO;
anotherNewsView.translatesAutoresizingMaskIntoConstraints = NO;
self.scrollView.pagingEnabled = YES;
[self.scrollView addSubview:newsView];
[self.scrollView addSubview:anotherNewsView];
NSDictionary *viewsDict = #{ #"newsView": newsView, #"anotherNewsView": anotherNewsView };
[self.scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[newsView]|"
options:NSLayoutFormatAlignAllTop | NSLayoutFormatAlignAllBottom
metrics:nil
views:viewsDict]];
[self.scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[newsView]|"
options:0
metrics:nil
views:viewsDict]];
[self.scrollView addConstraint:[NSLayoutConstraint constraintWithItem:newsView
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:self.scrollView
attribute:NSLayoutAttributeWidth
multiplier:1.0f
constant:0.0f]];
[self.scrollView addConstraint:[NSLayoutConstraint constraintWithItem:newsView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:self.scrollView
attribute:NSLayoutAttributeHeight
multiplier:1.0f
constant:0.0f]];
[self.scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[anotherNewsView(200)]|" options:0 metrics:nil views:viewsDict]];
[self.scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[anotherNewsView(100)]|" options:0 metrics:nil views:viewsDict]];
[self.scrollView setContentSize:CGSizeMake(self.scrollView.frame.size.width * 2, self.scrollView.frame.size.height)];
Right now the app looks like this. What I want is user should be able to scroll to the right and see the blue screen. What constraint do I need to add?
The constraints you have set the blue view to fill the scroll view AND to be of a fixed width, which causes a conflict. The |s at either end of the constraint string make anotherNewsView hug the bounds of its superview (scrollView).
Try dropping the final |s from your constraints. Also position anotherNewsView to be left-aligned with newsView rather than scrollView.
These constraints should do the trick:
[self.scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[newsView(500)]" options:0 metrics:nil views:viewsDict]];
[self.scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[newsView(500)][anotherNewsView(100)]" options:0 metrics:nil views:viewsDict]];
[self.scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[anotherNewsView(200)]" options:0 metrics:nil views:viewsDict]];

How come my constraints are not working? iOS

I am just trying to set a width and a height constraint on my imgView. I have the following code:
UIImageView *imgView = [[UIImageView alloc] init];
imgView.center = self.view.center;
imgView.image = [UIImage imageNamed:#"favorites5-highlighted.png"];
imgView.contentMode = UIViewContentModeCenter;
[self.view addSubview:imgView];
// width constraint
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:[imgView(==100)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(imgView)]];
// height constraint
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[imgView(==100)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(imgView)]];
I end up getting this error: Unable to simultaneously satisfy constraints.
I looked up SO and tried adding:
imgView.translatesAutoresizingMaskIntoConstraints = NO;
and
self.view.translatesAutoresizingMaskIntoConstraints = NO;
EDIT:
I am just trying to set the width and height of the imageView to the desired properties. I first tried using
CGRect rect = CGRectMake(0, 0, 10, 10);
imgView.frame = rect;
but nothing changed
Two issues:
You do want:
imgView.translatesAutoresizingMaskIntoConstraints = NO;
You do not want:
self.view.translatesAutoresizingMaskIntoConstraints = NO;
Your attempt to set center is for naught, as when constraints are applied, this will be lost. Unfortunately, you don't define constraints to define the location of the image view. You might do that with the last two lines of the block below:
UIImageView *imgView = [[UIImageView alloc] init];
//imgView.center = self.view.center; // this does nothing in auto layout
imgView.image = [UIImage imageNamed:#"favorites5-highlighted.png"];
imgView.contentMode = UIViewContentModeCenter;
imgView.translatesAutoresizingMaskIntoConstraints = NO; // add this line
[self.view addSubview:imgView];
NSDictionary *views = NSDictionaryOfVariableBindings(imgView);
// width constraint
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:[imgView(100)]" options:0 metrics:nil views:views]];
// height constraint
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[imgView(100)]" options:0 metrics:nil views:views]];
// center align
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:imgView attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:imgView attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0]];

Autolayout "conflicting constraints"

I've created a custom UIView that I use modally to display a UIToolbar and UIPickerView. I'm trying to make it very reusable, so I'm creating all of the UI in code, including setting constraints.
Here's the method body I use to set the view up to be added to another view, and then to animate the controls up onto the view from the bottom.
My view hierarchy is as follows:
"Owner" view (view to which this view is added):<br>
|-->"Background" view (set to the full size of "Owner", but mainly used as a dimmed background)<br>
|-->"Container" view (view which holds the toolbar and picker)
|--> Toolbar
|--> Picker
Here's the code I use to set up the UI:
- (void)prepareForView:(UIView *)view {
UIView *containerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
[containerView setTranslatesAutoresizingMaskIntoConstraints:NO];
self.containerView = containerView;
UIPickerView *picker = [[UIPickerView alloc] initWithFrame:CGRectMake(0, 0, self.frame.size.width, 162.0f)];
[picker setTranslatesAutoresizingMaskIntoConstraints:NO];
self.picker = picker;
UIToolbar *toolbar = [[UIToolbar alloc] initWithFrame:CGRectMake(0, 0, self.frame.size.width, 44.0f)];
[toolbar setTranslatesAutoresizingMaskIntoConstraints:NO];
UIBarButtonItem *done = ...;
UIBarButtonItem *flexSpace = ...;
UIBarButtonItem *cancel = ...;
toolbar.items = #[done, flexSpace, cancel];
[containerView addSubview:picker];
[containerView addSubview:toolbar];
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|[picker]|" options:NSLayoutFormatAlignAllBaseline metrics:nil views:NSDictionaryOfVariableBindings(picker)]];
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|[toolbar]|" options:NSLayoutFormatAlignAllBaseline metrics:nil views:NSDictionaryOfVariableBindings(toolbar)]];
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[toolbar(==44)][picker(==162)]|" options:NSLayoutFormatAlignAllLeading metrics:nil views:NSDictionaryOfVariableBindings(toolbar, picker)]];
[containerView layoutIfNeeded];
[self addSubview:containerView];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|[containerView]|" options:NSLayoutFormatAlignAllBaseline metrics:nil views:NSDictionaryOfVariableBindings(containerView)]];
self.containerTop = [NSLayoutConstraint constraintWithItem:containerView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeTop multiplier:1.0f constant:self.frame.size.height];
[self addConstraint:self.containerTop];
[self layoutIfNeeded];
}
Basically, I want containerView to be sized according to its contents (which should be a static 206 points). Then, I set a vertical space constraint between its top and the top of its superview (which is self). Later, I animate changing that so that the toolbar and picker "slide up" onto the screen.
Here's the animation code (the error is always triggered BEFORE this point):
// Add the view as a subview
[view addSubview:self];
// Setup view for display (here's what triggers the message)
[self prepareForView:view];
// Animate into view
[UIView animateWithDuration:animated?0.4f:0.0f
animations:^{
self.alpha = 1.0f;
}
completion:^(BOOL finished) {
// Now, slide the container view in from the bottom of the screen
self.containerTop.constant = self.frame.size.height - self.containerView.frame.size.height;
[UIView animateWithDuration:animated?0.4f:0.0f
animations:^{
[self layoutIfNeeded];
}
completion:^(BOOL finished) {
if (postDisplay != nil) {
postDisplay();
}
}
];
}
];
This is currently displaying correctly on all simulators and devices in testing, but I hate having warnings/errors, and I'm worried that this might NOT work as desired at some point.
Here's the actual error message that's displayed (I'm pretty sure this is exactly the same error every time):
Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want.
Try this: (1) look at each constraint and try to figure out which you don't expect; (2) find the code that added the unwanted constraint or constraints and fix it. (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints)
(
"<NSLayoutConstraint:0x170283430 V:|-(0)-[UIToolbar:0x137590850] (Names: '|':UIView:0x170382b10 )>",
"<NSLayoutConstraint:0x170283480 V:[UIToolbar:0x137590850(44)]>",
"<NSLayoutConstraint:0x174084ba0 V:[UIToolbar:0x137590850]-(0)-[UIPickerView:0x137586100]>",
"<NSLayoutConstraint:0x174081fe0 V:[UIPickerView:0x137586100(162)]>",
"<NSLayoutConstraint:0x17409bbc0 V:[UIPickerView:0x137586100]-(0)-| (Names: '|':UIView:0x170382b10 )>",
"<NSLayoutConstraint:0x174095b80 V:[UIView:0x170382b10(736)]>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x174084ba0 V:[UIToolbar:0x137590850]-(0)-[UIPickerView:0x137586100]>
What I don't understand is that all of the constraints ARE expected, and then the displayed UI is what I intend. When I inspect the view's layout with po [self.containerView recursiveDescription] in the debugger, I see the frames being exactly what I think they ought to be. Where am I going wrong?
The "Container" view appears to have a height of 736 from the line below:
<NSLayoutConstraint:0x174095b80 V:[UIView:0x170382b10(736)]>
You have laid out the view as below:
------ Top of UIView ------- (y = 0)
- 0 Space -
UIToolBar (y = 0 to y = 44)
- 0 Space -
UIPickerView (y = 44 to y = 206) ***
- 0 Space -
------ Bottom of UIView ----- (y = 736)
*** This is where the conflict arrises. This can't be 0 space away from the bottom of the UIView with all of the other constraints set the way that they are or the height of the UIView cannot be what it is with all of the other constraints.
EDIT:
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|[containerView]|" options:NSLayoutFormatAlignAllBaseline metrics:nil views:NSDictionaryOfVariableBindings(containerView)]];
When you use "|" within a the visualFormat for a constraint this is the parent view. Within this line you are saying you want the parent view to 0 space to the top of the [containerView] and also 0 space from the bottom of the [containerView]. This makes the container view the same height as the parent.
Edit #2:
Look at the following. I believe this is close to what you are trying to accomplish.
[containerView addSubview:picker];
[containerView addSubview:toolbar];
[self addSubview:containerView];
// Height & Width for containerView
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:[NSString stringWithFormat:#"V:[containerView(%f)]", containerView.frame.size.height] options:nil metrics:nil views:#{#"containerView":containerView}]];
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:[NSString stringWithFormat:#"H:[containerView(%f)]", containerView.frame.size.width] options:nil metrics:nil views:#{#"containerView":containerView}]];
// Height & Width for picker
[picker addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:[NSString stringWithFormat:#"V:[picker(%f)]", picker.frame.size.height] options:nil metrics:nil views:#{#"picker":picker}]];
[picker addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:[NSString stringWithFormat:#"H:[picker(%f)]", picker.frame.size.width] options:nil metrics:nil views:#{#"picker":picker}]];
// Height & Width for toolbar
[toolbar addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:[NSString stringWithFormat:#"V:[toolbar(%f)]", toolbar.frame.size.height] options:nil metrics:nil views:#{#"toolbar":toolbar}]];
[toolbar addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:[NSString stringWithFormat:#"H:[toolbar(%f)]", toolbar.frame.size.width] options:nil metrics:nil views:#{#"toolbar":toolbar}]];
// Vertical Positioning of picker & toolbar in containerView
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-0-[picker]" options:nil metrics:nil views:#{#"picker":picker}]];
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[toolbar]-0-|" options:nil metrics:nil views:#{#"toolbar":toolbar}]];
// Horizontal Positioning of picker & toolbar in containerView
[containerView addConstraint:[NSLayoutConstraint constraintWithItem:containerView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:picker attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0.0]];
[containerView addConstraint:[NSLayoutConstraint constraintWithItem:containerView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:toolbar attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0.0]];
// Center containerView (X/Y) in parent (self)
[self addConstraint:[NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:containerView attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0.0]];
[self addConstraint:[NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:containerView attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0]];
Edit #3: Using 'views' and 'metrics' dictionaries.
NSDictionary *views = #{#"containerView":containerView, #"picker":picker, #"toolbar":toolbar};
NSDictionary *metrics = #{#"hCV":containerView.frame.size.height,
#"wCV":containerView.frame.size.width,
#"hP":picker.frame.size.height,
#"wP":picker.frame.size.width,
#"hT":toolbar.frame.size.height,
#"wT":toolbar.frame.size.width};
[containerView addSubview:picker];
[containerView addSubview:toolbar];
[self addSubview:containerView];
// Height & Width for containerView
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[containerView(hCV)]" options:0 metrics:metrics views:views]];
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:[containerView(wCV)]" options:0 metrics:metrics views:views]];
// Height & Width for picker
[picker addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[picker(hP)]" options:0 metrics:metrics views:views]];
[picker addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:[picker(wP)]" options:0 metrics:metrics views:views]];
// Height & Width for toolbar
[toolbar addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[toolbar(hT)]" options:0 metrics:metrics views:views]];
[toolbar addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:[toolbar(wT)]" options:0 metrics:metrics views:views]];
// Vertical Positioning of picker & toolbar in containerView
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-0-[picker]" options:0 metrics:nil views:views]];
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[toolbar]-0-|" options:0 metrics:nil views:views]];
// Horizontal Positioning of picker & toolbar in containerView
[containerView addConstraint:[NSLayoutConstraint constraintWithItem:containerView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:picker attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0.0]];
[containerView addConstraint:[NSLayoutConstraint constraintWithItem:containerView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:toolbar attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0.0]];
// Center containerView (X/Y) in parent (self)
[self addConstraint:[NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:containerView attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0.0]];
[self addConstraint:[NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:containerView attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0]];

Resources