autolayout - figuring out which constraints supersede which - ios

I'm messing around with a really simple app to learn how to use AVFoundation (only been coding at all for about 14 weeks).
Included is a screenshot to help visualize my problem - my vertical constraints work just fine, and my horizontal constraints appear for two buttons that I have. However, my horizontal constraints (which I'm using to center a few objects) do not seem to work for the two labels I have underneath each button.
I wonder if the problem is that some constraints (perhaps the way I've created them) take priority over others and prevent some constraints from appearing properly? Really not sure here.
-(void)setConstraints {
[self.view removeConstraints:self.view.constraints];
UIButton *cameraButton = self.cameraButton;
UILabel *camera = self.videoLabel;
UIButton *libraryButton = self.libraryButton;
UILabel *library = self.libraryLabel;
NSDictionary *views = NSDictionaryOfVariableBindings(camera, cameraButton, libraryButton, library);
//set up top button to be horizontally centered
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"|-[cameraButton]-|"
options:0
metrics:nil
views:views];
//set up top button vertical from top of superview
constraints = [constraints arrayByAddingObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat: #"V:|-175-[cameraButton]"
options:0
metrics:nil
views:views]];
//set up top button label to be horizontally centered
constraints = [constraints arrayByAddingObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat: #"|-[camera]-|"
options:0
metrics:nil
views:views]];
//set up second button to be horizontally centered
constraints = [constraints arrayByAddingObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat: #"|-[libraryButton]-|"
options:0
metrics:nil
views:views]];
//set up label for second button to be horizontally centered
constraints = [constraints arrayByAddingObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat: #"|-[library]-|"
options:0
metrics:nil
views:views]];
//set up vertical constraints by spacing ALL objects appropriately
constraints = [constraints arrayByAddingObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat: #"V:[cameraButton]-[camera]-150-[libraryButton]-[library]"
options:0
metrics:nil
views:views]];
[self.view addConstraints:constraints];
}

My guess is you wanted these views to appear centered. But that isn't what a "|-[view]-|" constraint will do. What you just told the view is, "I want you to fill the entire width of your superview, modulo the default padding". If you were to set a background color on your labels you'd see them extending the entire width of the view.
The easiest solution for you here is to set the text layout to center.

Related

how to avoid hardcoding with programmatic autolayout

I have an issue when using autolayout (I'm new to it) where, although my constraints function as expected (everything centered horizontally, vertical spacing as I want it), when I move to landscape orientation, the bottom button disappears.
I understand that this happens because I've constrained my objects based on a portrait orientation view, and this no longer applies when the height and width values shift as we move to landscape. I just don't really know how to account for these changes when changing orientation. Any advice?
code and screenshot below:
-(void)setConstraints {
[self.view removeConstraints:self.view.constraints];
UIButton *cameraButton = self.cameraButton;
UILabel *camera = self.videoLabel;
UIButton *libraryButton = self.libraryButton;
UILabel *library = self.libraryLabel;
NSDictionary *views = NSDictionaryOfVariableBindings(camera, cameraButton, libraryButton, library);
NSDictionary *metrics = #{#"horizontalSpacing":#500.0, #"verticalSpacing":#125};
//set up top button to be horizontally centered
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"|-[cameraButton]-|"
options:0
metrics:nil
views:views];
//set up top button vertical from top of superview
constraints = [constraints arrayByAddingObjectsFromArray:
[NSLayoutConstraint constraintsWithVisualFormat: #"V:|-175-[cameraButton]"
options:0
metrics:nil
views:views]];
//set up top button label to be horizontally centered
constraints = [constraints arrayByAddingObjectsFromArray:
[NSLayoutConstraint constraintsWithVisualFormat: #"|-[camera]-|"
options:0
metrics:nil
views:views]];
//set up second button to be horizontally centered
constraints = [constraints arrayByAddingObjectsFromArray:
[NSLayoutConstraint constraintsWithVisualFormat: #"|-[libraryButton]-|"
options:0
metrics:nil
views:views]];
//set up label for second button to be horizontally centered
constraints = [constraints arrayByAddingObjectsFromArray:
[NSLayoutConstraint constraintsWithVisualFormat: #"|-[library]-|"
options:0
metrics:nil
views:views]];
//set up vertical constraints by spacing ALL objects appropriately
constraints = [constraints arrayByAddingObjectsFromArray:
[NSLayoutConstraint constraintsWithVisualFormat: #"V:[cameraButton]-[camera]-verticalSpacing-[libraryButton]-[library]"
options:0
metrics:metrics
views:views]];
self.libraryLabel.textAlignment = NSTextAlignmentCenter;
self.videoLabel.textAlignment = NSTextAlignmentCenter;
self.libraryLabel.backgroundColor = [UIColor redColor];
[self.view addConstraints:constraints];
}
You are getting exactly what you asked for. If that isn't what you want, don't ask for that!
Think about your vertical constraints:
#"V:|-175-[cameraButton]"
#"V:[cameraButton]-[camera]-verticalSpacing-[libraryButton]-[library]"
That constitutes a single chain of constraints from the top down. Naturally, if the screen is shorter than your 175 plus your verticalSpacing plus the sizes and minimal spacings of the other views, the bottom view(s) will be off the bottom of the screen.
If that isn't what you want, change your design so that isn't what you get. For example, position some of your views from the top down and some of your views from the bottom up. Or allow some of your spaces to change as a percentage of the superview height.

App crashing while adding constraints programatically

I am using visual format to define constraints. The goal is to place a UIView at the bottom of the super view say self.view with height fixed as 40, and width automatically. I have done this using Storyboard but I am unable to do it programatically. Here is the code what I have written. The app is crashing if i am not giving a fixed width. It is crashing with the following constraint: "H:[redView]|". If I change this to "H:[redView(100)]", it works. I don't want to use self.bounds and get width from there. It should stick from left side of super view, bottom and right side of the view.
Please help!
NSDictionary *viewsDictionary = #{#"redView":self.redView};
NSArray *constraint_H = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[redView(40)]"
options:0
metrics:nil
views:viewsDictionary];
NSArray *constraint_V = [NSLayoutConstraint constraintsWithVisualFormat:#"H:[redView]|"
options:0
metrics:nil
views:viewsDictionary];
// 3. Define the redView Position
NSArray *constraint_POS_V = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[redView]|"
options:0
metrics:nil
views:viewsDictionary];
NSArray *constraint_POS_H = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|[redView]"
options:0
metrics:nil
views:viewsDictionary];
[self.redView addConstraints:constraint_H];
[self.redView addConstraints:constraint_V];
[self.view addConstraints:constraint_POS_H];
[self.view addConstraints:constraint_POS_V];
You need to add redView to its super view first.
Also, the constraints are to be added to the superview, not redView.
[self.view addSubview:self.redView];
// create your constraints here
[self.view addConstraints:#[constraint_H, constraint_V, constraint_POS_V, constraint_POS_H]];
[self.view updateConstraintsIfNeeded];
In the visual constraints syntax the | refers to the parent view. The way you had that horizontal constraint before, adding it to the redview, it was interpreted as redview had a child, called the same, which obviously did not exist, so it crashed.
The vertical constraint works the way it is because it does not reference the parent view.
You could change your code a bit so that the constraints are more clear:
NSArray *verticalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[redView(40)]|" options:0 metrics:nil
views:viewsDictionary];
NSArray *horizontalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|[redView]|" options:0 metrics:nil
views:viewsDictionary];
[self.view addConstraints:horizontalConstraints];
[self.view addConstraints:verticalConstraints];
Just remember you should always add the constraints for an UIView to it's superview.
Changing this line
[self.redView addConstraints:constraint_V];
with
[self.view addConstraints:constraint_V];
Solved myself. Thanks to me..:)
If you want to take the width of the view and apply it to red view, you only need to add a constraint like that:
NSArray *constraint_POS_H = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[redView]-|"
options:0
metrics:nil
views:viewsDictionary];

Using NSLayoutConstraints to position a view outside the bounds of its superview

So I have a super view called self.content and I'm adding 2 subviews to it, namely self.bg1 and self.bg2 respectively. Initially I want self.bg2 to be sitting right outside the right bound of self.content. Users should be able to call in self.bg2 using a button. When the button is tapped self.bg1 moves to a position such that its right edge is touching the left edge of self.content. Here's the code that I've written, but it seems to only work when the user switches from self.bg1 to self.bg2(doesn't work backwards)
//BG1
self.bg1 = [[UIView alloc]init];
[self.bg1 setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.content addSubview:self.bg1];
//BG2
self.bg2 = [[UIView alloc]init];
[self.bg2 setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.content addSubview:self.bg2];
self.viewsDictionary = #{#"bg1":self.bg1,
#"bg2":self.bg2};
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:[NSString stringWithFormat:#"H:[bg1(%f)]",self.view.frame.size.width] options:0 metrics:nil views:self.viewsDictionary]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[bg1]|" options:0 metrics:nil views:self.viewsDictionary]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:[NSString stringWithFormat:#"H:[bg2(%f)]",self.view.frame.size.width] options:0 metrics:nil views:self.viewsDictionary]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[bg2]|" options:0 metrics:nil views:self.viewsDictionary]];
//self.bGConstraint is an NSArray
self.bGConstraint = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|[bg1]-[bg2]" options:0 metrics:nil views:self.viewsDictionary];
[self.view addConstraints: self.bGConstraint];
And in the switchToBG1 method I have:
[self.view removeConstraint:[self.bGConstraint objectAtIndex:0]];
self.bGConstraint = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|[bg1]-[bg2]" options:0 metrics:nil views:self.viewsDictionary];
[self.view addConstraints: self.bGConstraint];
Similarly in switchToBG2:
[self.view removeConstraint:[self.bGConstraint objectAtIndex:0]];
self.bGConstraint = [NSLayoutConstraint constraintsWithVisualFormat:#"H:[bg1]-[bg2]|" options:0 metrics:nil views:self.viewsDictionary];
[self.view addConstraints: self.bGConstraint];
Finally layoutIfNeeded in a UIView AnimationWithDuration...
*EDIT:
The animation when switching from BG1 to BG2 (i.e. left to right) is carried out successfully without any error, but when I try to go from right to left (BG2 back to BG1) the system throws me an Unable to simultaneously satisfy constraints. error message.
What am I doing wrong here? Thanks in advance!
What you are doing looks like the right idea, so there is probably just something wrong with your constraint-swapping logic, which causes something in your second constraint to conflict with something in your first set of constraints which are already there. To prevent that easily, structure it this way:
Up front, store all the constraints that position bg1 and bg2 for one position in one property, and all the constraints that position them for the other position in another property. Now when the time comes to swap, swap out all the old ones and swap in all the new ones. That way you will not confuse yourself so much, and you are guaranteed of consistency.
In other words, your code, which switches just one constraint, is trying to be too clever. It is better to keep it simple and stupid (and to have it work correctly)!

iOS constraints not updating as expected

I have a superview with circle view and a holderview that contains 3 labels as subview and is centred to the superview as seen in image
I have added constraints to the 3 labels with respect to holderview and also added constraints to holderview with respect to superview
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(titleLabel);
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"|-[titleLabel]-|"
options:0
metrics:nil
views:viewsDictionary];
[holderView addConstraints:constraints];
viewsDictionary = NSDictionaryOfVariableBindings(setLabel);
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"|-[setLabel]-|"
options: 0
metrics:nil
views:viewsDictionary];
[holderView addConstraints:constraints];
viewsDictionary = NSDictionaryOfVariableBindings(repLabel);
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"|-[repLabel]-|"
options:0
metrics:nil
views:viewsDictionary];
[holderView addConstraints:constraints];
viewsDictionary = NSDictionaryOfVariableBindings(titleLabel, setLabel, repLabel);
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-[titleLabel]-0-[setLabel]-0-[repLabel]-|"
options:0
metrics:nil
views:viewsDictionary];
[holderView addConstraints:constraints];
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(_labelView);
NSArray *constraints =[NSLayoutConstraint constraintsWithVisualFormat:#"|-[_labelView]-|"
options:0
metrics:nil
views:viewsDictionary];
[self addConstraints:constraints];
There is a feature in app where the circle shrinks. I want the holderview and its subivews to shrink dynamically. Adding the constraints works for holderview but the subviews get misaligned.
To shrink i update the frame size of the holderview as the superview frame changes.
Can anyone point out the mistakes and guide me to proper solution ?
Using auto layout and changing frame property messes up things.
Create oultest to the constraints that you want to change or animate
__weak IBOutlet UIView *settingsView;
__weak IBOutlet NSLayoutConstraint *settingsBottomConstraint;
__weak IBOutlet NSLayoutConstraint *settingsViewHeightConstraint;
Update the constrains(Never the frame!)
settingsBottomConstraint.constant = - settingsViewHeightConstraint.constant;
[settingsView setNeedsUpdateConstraints];
[settingsView layoutIfNeeded];
isSettingsHidden = YES;
Recently I have worked with animation of views with autolayout and you can find your answer here
Auto Layout constraint change does not animate
You can also use the function updateConstraints.
[settingsView updateConstraints];

AutoLayout UIScrollView Content Does Not Resize On Rotation (incl. sample project)

I'm trying to wrap my head around AutoLayout works and I think I understand most of it except for UIScrollViews. When I add those views to a project, they refuse to expand the dimensions of the screen. They will appear fine in the iPhone vertical view but when rotated, they will not expand. Also, when you launch the project in the iPad simulator, the UIScrollView screens will not expand to the dimensions of the iPad. The actual UIScrollView expands but it's content does not expand.
I've followed the instructions from Apple (https://developer.apple.com/library/ios/technotes/tn2154/_index.html) and while that fixed my dynamic content height issue, it has not solved the issue where the UIScrollView content does not expand to match the width of the screen. I've read that I need to pin the inner child of the scrollview to the right edge of the UIView but that seems like a bit of a hack and that also seems to contradiction the above Apple documentation.
Here's what I have:
UI and therefore the main View is created in a nib.
The view is a regular UIView. Not a UIScrollView
UIScrollView is created and added in viewDidLoad
All dynamic content is drawn in the nib and is stored in a separate UIView name contentView
contentView is added to scrollView in viewDidLoad
Constraints are added in viewDidLoad that pins the scrollView and contentView to the edges of their superViews.
Code from viewDidLoad:
- (void)viewDidLoad
{
[super viewDidLoad];
UIView* contentView = self.contentView;
UIScrollView * scrollView = [[UIScrollView alloc] init];
[self.view addSubview:scrollView];
[scrollView addSubview:contentView];
//remove auto contraints
[scrollView setTranslatesAutoresizingMaskIntoConstraints:NO];
[contentView setTranslatesAutoresizingMaskIntoConstraints:NO];
// Set the constraints for the scroll view and the image view.
NSDictionary* viewsDictionary = NSDictionaryOfVariableBindings(scrollView, contentView);
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[scrollView]|" options:0 metrics: nil views:#{#"scrollView":scrollView}]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[scrollView]|" options:0 metrics: nil views:#{#"scrollView":scrollView}]];
[scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[contentView]|" options:0 metrics: nil views:viewsDictionary]];
[scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[contentView]|" options:0 metrics: nil views:viewsDictionary]];
}
You can download the sample project from here: TestAutoLayout.zip
Does anyone have any idea why the scrollView content does not expand to the width of self.view despite the adding of the constraints?
As explained in this answer: https://stackoverflow.com/a/16843937/950953, linking the contentView to the width of main view seems to be the only thing that works.
I modified the constraint code from above with this section of code and the screen now lays out correctly.
UIView *mainView = self.view;
// Set the constraints for the scroll view and the image view.
NSDictionary* viewsDictionary = NSDictionaryOfVariableBindings(scrollView, contentView, mainView);
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[scrollView]|" options:0 metrics: 0 views:viewsDictionary]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[scrollView]|" options:0 metrics: 0 views:viewsDictionary]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[contentView]|" options:0 metrics: 0 views:viewsDictionary]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[contentView]|" options:0 metrics: 0 views:viewsDictionary]];
//hack to tie contentView width to the width of the screen
[mainView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:[contentView(==mainView)]" options:0 metrics:0 views:viewsDictionary]];
If anyone can find a proper solution, please post it.
I was trying to do something similar. I eventually tried the "hack" you resorted to, but it didn't work for me because I ran into some kind of constraints conflict. However I was finally able to get it to work by adding just one line:
[contentView setAutoresizingMask:UIViewAutoresizingFlexibleWidth];

Resources