Auto-layout pinning two subviews together - ios

Disclaimer: I'm comparably new to iOS development. This is my very first try on Auto-layout.
I have a container UIView that contains one UIImageView and a UILabel
When I click on the UIButton in the bottom of the view controller, I'd want the UIImageView to disappear and UILabel to automatically go to the top.
P.S. Please introduce me to some good resources to learn Auto-layout principles. Thank you

If you can figure out your starting and ending layout constraints, then you can animate it using this template:
// add/remove constraints here or even change the constants of a current constraint
[view setNeedsUpdateConstraints];
[UIView animateWithDuration:0.25f animations:^{
[view layoutIfNeeded];
}];
Have a read of the Cocoa AutoLayout Guide
Also watch the WWDC videos on AutoLayout. WWDC 2012 232 Auto Layout by Example is a great one.

The easiest way will be to
define a constraint for the height of the image view;
define an IBOutlet for that constraint; and
when you tap on the button, set the constant for that imageview's height constraint to be 0, and then do the layoutIfNeeded as recommended by bandejapaisa:
[UIView animateWithDuration:0.25f animations:^{
[view layoutIfNeeded];
}];
If all of your other constraints are defined properly (e.g. the button's top constraint is to the image view and not the superview, the button has no bottom constraint to the superview, etc.), then this should yield the desired effect. Also, if you change the image view's contentMode to something like "aspect fill", then make sure you define the image view to clip subviews (so that you don't have the image spilling outside of the zero height image view).

Related

Making one UIView track the animation of a single constraint on another

I'm trying to learn how to animate using AutoLayout constraints.
I've got two UIViews, as below:
When I tap the "Up" button, I want the result to look like this. I want the red rectangle to stay the same size, but to stay pinned to the top of the gray rectangle and go along for the ride, like this:
Instead, I get this:
Here's my code:
- (IBAction)upButton:(UIButton *)sender
{
self.heightConstraint.constant = 20;
[UIView animateWithDuration:1.0
animations:^{
[self.view layoutIfNeeded];
} completion:nil];
}
- (IBAction)downButton:(UIButton *)sender
{
self.heightConstraint.constant = 438;
[UIView animateWithDuration:1.0
animations:^{
[self.view layoutIfNeeded];
} completion:nil];
}
The constraint situation looks like this (screenshot--I don't know how to copy the actual list from the Document Outline). The relevant animated constraint (self.heightConstraint) is highlighted:
I confess that I find Auto Layout Constraints sort of like playing Whack-A-Mole. Can someone please help me understand how I should go about this?
When setting constraints you need to think about how the various items relate to each other and keep in mind that the layout engine needs to be able to compute a top,left and width/height for each item. Too many constraints can be just as problematic as insufficient constraints.
In your case you want the size of the red box to be fixed, so I would set constraints on it for width and height. You also want its position relative to the left edge of the screen to be fixed, so set a leading space constraint to the superview. You want its space to the grey box to be fixed, so set a trailing space constraint to the grey box. Finally you want the top of the red box to be the same as the grey box, so select the orange box in IB, Control-drag to the grey box and select "Top" from the pop up.
For the grey box, set constraints for trailing space to the superview and bottom space to the superview (it will already have a leading space constraint to the red box). Finally, either set a constraint for height or a constraint for top space to superview. This is the constraint you will animate, so create an IBOutlet for it and change it in your code as required.
I'd suggest fixing the height of the orange box. If you have a bottom constraint for the orange box remove it.
These are the constraints I would use on the orange box:
Top to the superview / Leading to the superview / Trailing to the grey box / Height fixed / Width fixed
These are the constraints I would use on the grey box:
Top to the superview / Leading to the orange box / Bottom to the superview / Width fixed
Notice the two constraints that I bolded essentially control each view's height.
I'd attach an IBOutlet to the top constraint of each view so that I could modify these values to match the behavior you want.

Autolayout aspect ratio based on `intrinsicContentSize` (not constant)

Is it possible to apply such auto layout constraints with aspect ratio calculated on the fly based on intrinsicContentSize?
In the documentation I've found only constrains with fixed ratio value.
Actual with and height from intrinsicContentSize is not important in my use case, I want preserve height and width ratio of the view which changes dynamically.
Should I provide my own implementation of the constraint? Or is there a better way?
The intrinsicContentSize is not available as input to constraints. (Logically, there are constraints that implement the intrinsicContentSize and related content-hugging and compression-resistance priorities, but that's different.)
If you want such an aspect ratio constraint, you'll have to add it yourself. It's easy enough to query the intrinsicContentSize at a given moment, verify that it provides real values (not NSViewNoInstrinsicMetric) for both dimensions, compute the aspect ratio, and then use that as the multiplier in a constraint that relates the item's width to its height.
The hard part is knowing when the intrinsicContentSize has been invalidated so you can remove the old aspect ratio constraint and add a new one. You can do that as a subclass of the view by overriding -invalidateIntrinsicContentSize. (Be sure to call through to super!) However, I don't know of a way to do that from a controller or superview.
You can find the answer on how to set up ratio-based constraints here. All you need is to constrain the width and the height together, maintaining a given aspect ratio (in that case 4/3).
We can debate whether it's a good thing views know this information or whether should their parents set this kind of constraints. I usually prefer parents to set constraints, but if your view doesn't make any sense without this constraint or all these views need this constraint, you can safely let these views manage their own width/height ratio.
Finally, intrinsicContentSize tells Auto Layout the size of the view when the view is alone:
Returns the natural size for the receiving view, considering only properties of the view itself.
I don't know what your view represents, but as the documentation says, you can return CGSizeMake(UIViewNoIntrinsicMetric, UIViewNoIntrinsicMetric):
If a custom view has no intrinsic size for a given dimension, it can return UIViewNoIntrinsicMetric for that dimension.
If you have the situation where you want to calculate the value of a constraint during runtime, the best option is to CTRL drag the constraint into the .h file of your controller and create an IBOutlet for it. This allows you to change the value of a constraint in code. You can even animate the change to a constraint value.
Then in your code at setup time or when an action occurs which might change the value you want (like loading a new image for example) you calculate the value you want for the constraint and set its value in the code. Usually you do this using:
self.myConstraintIBOutlet.constant = <insert your new value>;
You may then need to mark the affected view as needing layout:
// Mark whole view as needing layout. You could do this in a subview if
// only a small area is affected
[self.view setNeedsLayout];
[self.view layoutIfNeeded];
If you want a smooth transition, you can put layoutIfNeeded inside an animation block causing it to animate the change of constraint:
[self.view setNeedsLayout];
[UIView animateWithDuration:.25 animations:^{
[self.view layoutIfNeeded];
} completion:^(BOOL finished) {
// Completion code
}];

Weird animation behaviour while using autolayout in .xib

I've a custom tableview cell with a right aligned button having following constraints:
Trailing space from superview
Fixed width
Fixed height
Top space from superview
Now when I launch my view controller, this edit icon image fly and settle down at the correct location. How can I remove that animation.
NOTE: This problem is in visible only in certain cells. I've also tried deleting and re-adding the component as well.
Fixed the problem by updating the frame and then calling the [self layoutIfNeeded];
Nothing will work if you miss [self layoutIfNeeded]; function call after updating your frames.
While working with autolayout, you should always call [self layoutIfNeeded]; after updating the frame.

Autolayout height changes when I push a new view controller

Edit: I fixed my problem, but I would be interested in understanding why my fix works. See below.
I use autolayout to build my UITableViewCells.
The cell is pretty simple with 2 labels (purple and yellow) and a textfield (green).
This works fine when displaying the first time. But when I push a new view controller, the view instantly rearranges itself. The purple label gets bigger for whatever reason.
Here is an example where I click the "Parent Account ID" cell to push a new view controller. Notice that as soon as the transition begins, the layout changes. When I come back it is still changed.
The items are created with [UILabel new] with no frame.
[self.contentView addSubview:self.textLabel];
[self.contentView addSubview:self.errorLabel];
[self.contentView addSubview:self.textField];
Then the autolayout is created using Masonry.
UIEdgeInsets insets = UIEdgeInsetsMake(3, 15, 3, 15);
[self.textLabel makeConstraints:^(MASConstraintMaker *make) {
make.left.top.equalTo(self.contentView).insets(insets);
make.width.priorityLow();
}];
[self.errorLabel makeConstraints:^(MASConstraintMaker *make) {
make.top.right.equalTo(self.contentView).insets(insets);
make.left.equalTo(self.textLabel.right).offset(5);
}];
[self.textField makeConstraints:^(MASConstraintMaker *make) {
make.left.bottom.right.equalTo(self.contentView).insets(insets);
make.top.equalTo(self.textLabel.bottom).insets(insets);
}];
Notice that I never specify a height because I don't really care about the height. (as long as it is consistent!)
Any idea why this could change? Thanks
Edit: I found a fix.
I now also set the autolayout properties of contentView.
[self.contentView makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self);
}];
I am still interested in understanding why the contentView changes its size!
Your fix works because it enforces a missing constraint.
First, let's start by writing down the constraints that you created using Masonry:
The purple view must be aligned left.
The purple view must be aligned top.
The purple view's width can vary but it's min and max aren't that important.
The yellow view must be aligned right.
The yellow view must be aligned top.
The yellow view's left edge must be 5px away from the purple view's right edge.
The green view must be aligned left.
The green view must be aligned bottom.
The green view must be aligned right.
The green view's top edge must be 0px away from the purple view's bottom edge.
This might seem complete but it allows autolayout to draw your views in many different ways.
Consider the following:
According to the rules you have created above, all three possibilities are true.
As you tap on a cell, the layout is invalidated and redrawn. Given that your cell can be drawn a few different ways, it will simply draw itself and satisfy all of your constraints... and it does.
I'll be honest, I'm not 100% sure what make.edges.equalTo(self); does exactly since it is a bit ambiguous. However, one thing is for sure is that it adds the missing constraints you were looking for.
Possible missing constraints are:
The purple view's height must be equal to the green view's height.
The yellow view's height must be equal to the purple view's height.
The green view's height must be equal to the yellow view's height.
Hope this clarifies it for you.

iOS auto layout, how to resize super view to fit all subviews

I'm having a problem an auto layout problem with my UI
At the bottom I have a container view with a height constraint of 0 which will contain some subviews at runtime. Also I have a IBOutlet to the container view's height constraint, at runtime when I know the height for the view, i set the height constraints constant property to the correct height. I want the super view to increase in size after the multiline detail labels text are set and the container views height constraint. What end up happening is the super view stays the same size and all the labels heights are decreased and the container view overlaps everything. Any help would be appreciated I've been at this for ages.
Thanks.
Do you call layoutIfNeeded after changing the constraint!?
Your code should look something like:
[[self buttonEdgeConstraint] setConstant:200];
[UIView animateWithDuration:0.5 animations:^{
[[self button] layoutIfNeeded];
}];

Resources