Resize subviews in Autolayout visual form - ios

I've failed on migrating my app from Autoresize to Autolayout mechanism. All I need is a view with dynamic size and subviews that will be connected to hte left top corner with the same size as their superview. In IB I've set size for PieView (My superview) as 186x186. And let the IB to generate all needed Constraints (I will create new ones in code):
PieView.m -updateConstraints()
- (void)updateConstraints {
[super updateConstraints];
[self removeConstraintsAffectingViewAndSubviews];
[self setTranslatesAutoresizingMaskIntoConstraints:NO];
CGFloat sizeCoef = 0.7f;
CGFloat percent = [self.category getFillinPercent] / 100.0f;
percent = percent > 1.0f ? 1.0f : percent;
sizeCoef += (1.0f - sizeCoef) * percent;
NSLayoutConstraint *width = [NSLayoutConstraint constraintWithItem:self
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0f
constant:100.f];//kDiameter * sizeCoef];
NSLayoutConstraint *height = [NSLayoutConstraint constraintWithItem:self
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0f
constant:100.0f];//kDiameter * sizeCoef];
//_imgEmptyCircle, _imgFullCircle - UIImageViews
NSDictionary *views = NSDictionaryOfVariableBindings(_imgEmptyCircle, _imgFullCircle, self);
NSDictionary *metrics = #{#"height":#100.0};
[self addConstraints:#[width, height]];
NSString *visualForm = #"H:|[_imgEmptyCircle(height)][_imgFullCircle(height)]|";
NSArray *horizontalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:visualForm
options:0
metrics:metrics
views:views];
visualForm = #"V:|[_imgEmptyCircle(height)][_imgFullCircle(height)]|";
NSArray *verticalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:visualForm
options:0
metrics:metrics
views:views];
[self addConstraints:verticalConstraints];
[self addConstraints:horizontalConstraints];
}
The result really surprised me. Blue one displayed correctly as I want, but green one has strange behavior.
Off course I have error: Unable to simultaneously satisfy constraints.
First imageView:
NSIBPrototypingLayoutConstraint:0xa2795d0 'IB auto generated at build time for view with fixed frame' V:[UIImageView:0x8a37150(186)]>
NSLayoutConstraint:0xa0305c0 V:[UIImageView:0x8a37150(100)]
Second imageView: the same one
Probable for PieView:
(
NSLayoutConstraint:0x8a40750 V:[CEPieView:0x8a36e50(100)],
NSLayoutConstraint:0xa030570 V:|-(0)-[UIImageView:0x8a37150] (Names: '|':CEPieView:0x8a36e50 ),
NSLayoutConstraint:0xa0305c0 V:[UIImageView:0x8a37150(100)],
NSLayoutConstraint:0xa0305f0 V:[UIImageView:0x8a37150]-(0)-[UIImageView:0xa277cf0]>
NSLayoutConstraint:0xa030620 V:[UIImageView:0xa277cf0(100)],
NSLayoutConstraint:0xa030650 V:[UIImageView:0xa277cf0]-(0)-| (Names: '|':CEPieView:0x8a36e50 )
)
Please, give me an advise how to fix it. How to create and old autoresizing mask(UIViewAutoresizingFlexibleHeight, UIViewAutoresizingFlexibleWidth) with the help of AutoLayout mechanism. Any help will be appreciated.

The way you're setting the constraints is wrong. Basically you're asking the superview to be 100pts by 100pts, and to contains two subviews that are each 100pts by 100pts and are beside AND on top of each other. So first your superview would have to be 200pts wide and/or tall, and then you can't have two view that are both beside each other and on top.
What you really want to do (I think) is having:
NSString *visualForm = #"H:|[_imgFullCircle]|"; // No need to explicitly set the height
Then add the corresponding constraint, then:
visualForm = #"H:|[_imgEmptyCircle(height)]|";
And add another constraint for this, and do the same for vertical constraints.
Basically you'd end up with:
//_imgEmptyCircle, _imgFullCircle - UIImageViews
NSDictionary *views = NSDictionaryOfVariableBindings(_imgEmptyCircle, _imgFullCircle, self);
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[_imgEmptyCircle]|"
options:0
metrics:nil
views:views]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[_imgFullCircle]|"
options:0
metrics:nil
views:views]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[_imgEmptyCircle]|"
options:0
metrics:nil
views:views]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[_imgFullCircle]|"
options:0
metrics:nil
views:views]];

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.

iOS How to auto layout a view 3 x screen width?

I tried to place a UIView which is 3x the screen width (or 3x UIScrollView's width) in UIScrollView.
UIScrollView's width equal to the screen width.
How do I do this in AutoLayout?
If you want to do it in code via pure AutoLayout, here's how you'd do it:
- (void)layoutUserInterface {
// Placing the scrollView using AutoLayout
[self.view addSubview:self.scrollView];
// Note: The "H:" is optional, but I like to be clear
[self.view addConstraints:
[NSLayoutConstraint
constraintsWithVisualFormat:#"H:|[scrollview]|"
options:0
metrics:nil
views:#{#"scrollview": self.scrollView}]];
[self.view addConstraints:
[NSLayoutConstraint
constraintsWithVisualFormat:#"V:|[scrollview]|"
options:0
metrics:nil
views:#{#"scrollview": self.scrollView}]];
// Placing the "wideView" using AutoLayout
[self.scrollView addConstraints:
[NSLayoutConstraint
constraintsWithVisualFormat:#"H:|[wideView]"
options:0
metrics:nil
views:#{#"wideView": self.wideView}]];
[self.scrollView addConstraints:
[NSLayoutConstraint
constraintsWithVisualFormat:#"V:|[wideView]"
options:0
metrics:nil
views:#{#"wideView": self.wideView}]];
// Setting up the 3x width constraint
[self.scrollView addConstraint:
[NSLayoutConstraint
constraintWithItem:self.wideView
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeWidth
multiplier:3.0f
constant:0.0f]];
// Figure out your "wideView's" height requirements
}
Assign a value for your views's width constraint and make an IBOutlet for this in your header file.
In your m file's viewDidLoad method get the width of the current device's screen, multiply by 3 and assign to your constraint's constant. And finally call your view's layoutIfNeeded method.
Is this clear enough or do you want me to make it more clear?

iAutoLayout crash when setting Visual Format Language

HI I am setting my views using autoLayouts with visual format language.
I add some constraints to my views.
Here are my codes how I set up:
- (void)viewDidLoad
{
self.first = [UIView new];
self.second = [UIView new];
self.third = [UIView new];
self.fourth = [UIView new];
self.fifth = [UIView new];
self.first.translatesAutoresizingMaskIntoConstraints = NO;
self.second.translatesAutoresizingMaskIntoConstraints = NO;
self.third.translatesAutoresizingMaskIntoConstraints = NO;
self.fourth.translatesAutoresizingMaskIntoConstraints = NO;
self.fifth.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.first];
[self.view addSubview:self.second];
[self.view addSubview:self.third];
[self.view addSubview:self.fourth];
[self.view addSubview:self.fifth];
NSDictionary *dictionary = NSDictionaryOfVariableBindings(_first, _second, _third, _fourth, _fifth);
NSArray *constraintsArray;
NSString *format;
format = #"|[_first][_second][_third][_fourth][_fifth]|";
constraintsArray = [NSLayoutConstraint constraintsWithVisualFormat:format options:NSLayoutFormatAlignAllLeft metrics:nil views:dictionary];
[self.view addConstraints:constraintsArray];
format = #"V:|[_first]|";
constraintsArray = [NSLayoutConstraint constraintsWithVisualFormat:format options:NSLayoutFormatAlignAllLeft metrics:nil views:dictionary];
[self.view addConstraints:constraintsArray];
format = #"V:|[_second]|";
constraintsArray = [NSLayoutConstraint constraintsWithVisualFormat:format options:NSLayoutFormatAlignAllLeft metrics:nil views:dictionary];
[self.view addConstraints:constraintsArray];
format = #"V:|[_third]|";
constraintsArray = [NSLayoutConstraint constraintsWithVisualFormat:format options:NSLayoutFormatAlignAllLeft metrics:nil views:dictionary];
[self.view addConstraints:constraintsArray];
format = #"V:|[_fourth]|";
constraintsArray = [NSLayoutConstraint constraintsWithVisualFormat:format options:NSLayoutFormatAlignAllLeft metrics:nil views:dictionary];
[self.view addConstraints:constraintsArray];
format = #"V:|[_fifth]|";
constraintsArray = [NSLayoutConstraint constraintsWithVisualFormat:format options:NSLayoutFormatAlignAllLeft metrics:nil views:dictionary];
[self.view addConstraints:constraintsArray];
}
Here is what i want the five UIViews act as. They have same WIDTH and HEIGHT and there are no margin on both size.(The first one and the last one) . That's say, they are filled within there container view, in this case, (self.view)
When I run, the app crash:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Unable to parse constraint format:
Options mask required views to be aligned on a horizontal edge, which is not allowed for layout that is also horizontal.
|[_first][_second][_third][_fourth][_fifth]|
^'
What I had done wrong?
Please help me. Thanks.
You should remove all of those references to NSLayoutFormatAlignAllLeft.
Also, unrelated, but you are missing the constraint that makes them the same width. I'd replace the horizontal constraint with:
format = #"H:|[_first][_second(==_first)][_third(==_first)][_fourth(==_first)][_fifth(==_first)]|";
format string proposed by Rob is perfect. You can also avoid all the other vertical constraints just adding 'NSLayoutFormatAlignAllTop' instead of 'NSLayoutFormatAlignAllLeft'.
Basically when you deal with a format string related to horizontal axis you must use alignment options that relate to vertical axis and vice versa ( option is perpendicular to format string ), or if you don't need it just pass 0.
Your only one method should look like this:
NSString format = #"H:|[_first][_second(==_first)][_third(==_first)][_fourth(==_first)][_fifth(==_first)]|";
[NSLayoutConstraint constraintsWithVisualFormat:format
options:NSLayoutFormatAlignAllTop
metrics:nil
views:dictionary];
[EDIT]
Rob's right. NSLayoutFormatAlignAllTop aligns all top side of views inside the format string between them and not to their superview. You have to add one vertical pin constraint to only one of your view and all the others will also align because of 'NSLayoutFormatAlignAllTop'. Also to remove ambiguous layout you need to add a height constraint. This is the code i tried (with a 100 point height) and doesn't give me ambiguous layout:
NSDictionary *dictionary = NSDictionaryOfVariableBindings(_first, _second, _third, _fourth, _fifth);
NSString *format = #"H:|[_first][_second(==_first)][_third(==_first)][_fourth(==_first)][_fifth(==_first)]|";
NSMutableArray *constraintsArray = [NSLayoutConstraint constraintsWithVisualFormat:format
options:NSLayoutFormatAlignAllTop
metrics:nil
views:dictionary].mutableCopy;
[constraintsArray addObject:[NSLayoutConstraint constraintWithItem:_first
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:_first.superview
attribute:NSLayoutAttributeTop
multiplier:1.0 constant:0]];
CGFloat height = 100;
for (UIView *view in #[_first,_second,_third,_fourth,_fifth]) {
[constraintsArray addObject:[NSLayoutConstraint constraintWithItem:view
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0
constant:height]];
}
[self.view addConstraints:constraintsArray];

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]];

UIView Awful Performance

I have built a slide navigation controller which allows the sliding of UIViewController's in a fast and smooth way (Facebook style). I have since added a UIViewController with a child view controller with it's own child view controller, as well as a table view. The lowest child view controller also has a view subviews. This UIViewController has the view that is made visible when the top view is slid away.
I have tested the smoothness both with and without this view controller. Without it the slide runs at a nice 60 frames per second, but with it my device is only managing a choppy 20 to 30 fps.
I have also tried removing subviews which seems to make a slight improvement, but not as much as I would expect. Even when switched out with another fairly complex view controller (without child view controls though) I get much better performance.
I am using auto layout throughout the app, and have come to believe this is the problem. None of my code is being executed whilst this poor sliding is occurring, so I believe it has something to do with the constraints being calculated as more of the view is being shown, but can't be sure.
As to avoid including the whole class, here are the updateViewConstraint methods in the view controller and subviews:
TOP VIEW CONTROLLER
/**
* Called when the view controller’s view needs to update its constraints.
*/
- (void)updateViewConstraints
{
[super updateViewConstraints];
// remove all constraints
[self.view removeConstraints:self.view.constraints];
NSArray *constraints;
// add the table view to the top of the view and the parameters view to the bottom
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(Panel)-[tableView]|"
options:kNilOptions
metrics:#{#"Panel": #(kPanelWidth)}
views:self.viewsDictionary];
[self.view addConstraints:constraints];
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|[tableView]-|"
options:kNilOptions
metrics:nil
views:self.viewsDictionary];
[self.view addConstraints:constraints];
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(PanelPlus)-[recipeParameters]-|"
options:kNilOptions
metrics:#{#"PanelPlus": #(kPanelWidth + 20)}
views:self.viewsDictionary];
[self.view addConstraints:constraints];
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[recipeParameters(==Height)]|"
options:kNilOptions
metrics:#{ #"Height" : #(kParametersControllerHeight)}
views:self.viewsDictionary];
[self.view addConstraints:constraints];
}
FIRST CHILD VIEW CONTROLLER
/**
* Called when the view controller’s view needs to update its constraints.
*/
- (void)updateViewConstraints
{
[super updateViewConstraints];
// remove all constraints
[self.view removeConstraints:self.view.constraints];
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|[pageView]|" options:kNilOptions
metrics:nil views:self.viewsDictionary];
[self.view addConstraints:constraints];
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|[pageView]|" options:kNilOptions
metrics:nil views:self.viewsDictionary];
[self.view addConstraints:constraints];
}
SECOND CHILD VIEW CONTROLLER
- (void)updateViewConstraints
{
[super updateViewConstraints];
NSArray *constraints;
// remove all constraints
[self.view removeConstraints:self.view.constraints];
if (self.interfaceOrientation == UIInterfaceOrientationPortrait || self.interfaceOrientation == UIInterfaceOrientationPortraitUpsideDown)
{
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-(8)-[optionLabel]-[selectionWheel]-[includeExclude(==32)]-|"
options:NSLayoutFormatAlignAllCenterX
metrics:nil
views:self.viewsDictionary];
[self.view addConstraints:constraints];
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[selectionWheel]-|"
options:kNilOptions
metrics:nil
views:self.viewsDictionary];
[self.view addConstraints:constraints];
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[includeExclude]-|"
options:kNilOptions
metrics:nil
views:self.viewsDictionary];
[self.view addConstraints:constraints];
}
else
{
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-[selectionWheel]-|" options:kNilOptions
metrics:nil views:self.viewsDictionary];
[self.view addConstraints:constraints];
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[optionLabel]-[selectionWheel]-(Balance)-|"
options:NSLayoutFormatAlignAllCenterY
metrics:#{#"Balance": #(self.optionLabel.bounds.size.width + 40)}
views:self.viewsDictionary];
[self.view addConstraints:constraints];
}
}
CUSTOM UICONTROL (SUBVIEW OF SECOND CHILD VIEW CONTROLLER)
/**
* Update constraints for the view.
*/
- (void)updateConstraints
{
[super updateConstraints];
[self removeConstraints:self.constraints];
[self.excludeButton removeConstraints:self.excludeButton.constraints];
[self.includeButton removeConstraints:self.includeButton.constraints];
NSArray *constraints;
NSLayoutConstraint *constraint;
// add everything to fill the bounds
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-(==6)-[excludeButton]-(==6)-|"
options:kNilOptions
metrics:nil
views:self.viewsDictionary];
[self addConstraints:constraints];
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|[excludeButton][optionLabel][includeButton]|"
options:NSLayoutFormatAlignAllTop | NSLayoutFormatAlignAllBottom
metrics:nil
views:self.viewsDictionary];
[self addConstraints:constraints];
// make the exclude and include buttons square
constraint = [NSLayoutConstraint constraintWithItem:self.excludeButton attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self.excludeButton attribute:NSLayoutAttributeHeight multiplier:1.0f constant:0.0f];
[self.excludeButton addConstraint:constraint];
constraint = [NSLayoutConstraint constraintWithItem:self.includeButton attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self.includeButton attribute:NSLayoutAttributeHeight multiplier:1.0f constant:0.0f];
[self.includeButton addConstraint:constraint];
}
The implementation of the top UIViewController is here.

Resources