Animating UIView frame doesn't respect constraints? - ios

What I want to achieve is when I tap button, animate frame to be resized so the bottom line position will not change and height will decrease. This part is happening. I also want upper label to move down with frame's upper boundary and lower label to stay put. So I placed constraint in IB for the lower label to have vertical space of 20 pixels and priority of 1000.
- (IBAction)tapTap:(id)sender {
//[self.containerView layoutIfNeeded];
[UIView animateWithDuration:1.5 animations:^{
CGRect rect = CGRectOffset(self.containerView.frame, 0, 30);
rect.size.height -= 30;
self.containerView.frame = rect;
//[self.containerView layoutIfNeeded];
} completion:NULL];
}
Lower label is animating as well and will not stay put. My best guess after trying layoutIfNeeded which didn't work, is that I can't rely on constraints while animating frame. If that is true, what will be solution?

The first rule of AutoLayout: Don't touch the frames (or the center).
It looks like you have already found the answer but the way to do this is to keep a reference to the constraints that you would like to change. For instance, if you want to move the view up and down then store a vertical constraint that sets the vertical position on the view. Now when you animate this constraint the view will animate with it.
self.topConstraint.constant = 50;
// etc...
You can create property references to constraints by CTRL-dragging them just like with buttons, labels, etc...

Related

Programmatically stretch UIButton width depending on condition

I've got two UIButtons into a view, which is 232px wide.
The buttons are aligned such as
|[BUTTON1][BUTTON1]|
|-------232px------|
I have a set of constraints for this alignment, but now I want to be able to change the frame of BUTTON1's to full width, depending on a condition, so it'd have to be programmatically. I tried changing the frame, but since it's automatically updated, it won't work. Basically, what I want to achieve is BUTTON1 covering BUTTON2, Is there an easy way to do this?
As you are using autoLayout in your project, so to change the frame of your button, you have to change the constraint of you UIButton.
First make the IBOutlet of your buttons' width constraint and then when you want to change the frame of the button update that constraint like this
button.widthConstraint.constant = //set the constraint;
[UIView animateWithDuration:0.25f
animations:^{
[self.view setNeedsLayout];
}
completion:^(BOOL finished) {
}];
Make the outlet of button and width constraint and then
self.buttonWidthConstraint += 20 // adjust as per requirements
[self.myButton updateConstraints]
You can set the constant of the button you want covered to 0 and animate the change. Then the covering button will fill in the whole space.

Animate to text height (autolayout)

I want to animate from a height of 0 to the height of the text (UILabel). I am using autolayout and I do not know how high the text will be. My approach was to start by setting a height=0 constraint to the text, and animate like this:
//retrieves the height constrain of the clicked item
NSLayoutConstraint *heightContraint = [heightConstraints objectAtIndex:sender.tag];
//activates/deactivates the constraint
heightContraint.active = !heightContraint.active;
//animates
[UIView animateWithDuration:3 animations:^{
[self layoutIfNeeded];
}];
My problem is, that with this approach, the text height does not animate, it changes from a height of 0 to the new height instantly. Only the position/size change of the containing views is animated. How I animate the text height change without knowing the height of the text??
I have found a solution. Instead of setting a constraint to the height, I set a containing view (clipping), which contains the UILabel and the view above. Then, I create 2 constraints that pin the bottom edges to the containing view, one related to the UILabel, and one related to the view above. I just activate/deactivate those constraints like this:
//get the constraints
NSLayoutConstraint *viewAboveTextConstraint =
[viewAboveTextConstraints objectAtIndex:sender.tag];
NSLayoutConstraint *uilabelContraint = [uilabelContraints objectAtIndex:sender.tag];
//flip the active states
viewAboveTextConstraint.active = !viewAboveTextConstraint.active;
uilabelContraint.active = !uilabelContraint.active;
//animate
[UIView animateWithDuration:.3 animations:^{
[self layoutIfNeeded];
}];

Animating a view's height with autolayout when there's no height constraint

I have a view (let's call it "contentView") that its height is determined by its subviews, which is mostly UILabels.
The UILabels content is dynamic and may grow or shrink at runtime.
The UILabels have numberOfLines = 0, and no height constraint, which allow their height to grow with the content.
The UILabels have 0 leading/trailing/top/bottom constraints to their neighbors and/or superview, as follows:
The result is having contentView's height equal the total height of all its sub UILabels (with their height being determined by the height of their text).
I now need to hide contentView on a press of a button (and show it again when the button is pressed again).
Also note, that while animating, if there are other views below "contentView" they need to move in to take the empty space (and move back out when the button is pressed again).
It seems the most natural animation is to change the height of contentView to 0, while animating the views under contentView up at the same time (with the reverse animation changing the height back to its original height).
Any suggestions on how to animate contentView's height?
Unfortunately I was not able to create a smooth enough animation for this layout, but I found a workaround.
Constraints change the layout while animating, and I needed the opposite effect - the layout had to remain fixed while the animation is in progress.
So I disabled the constraints for the duration of the animation, and that seemed to work well.
The full recipe is:
I disabled the constraints on contentView:
contentView.translatesAutoresizingMaskIntoConstraints = YES
I then did a simple frame animation. I made sure to calle layoutIfNeeded on superview to make sure the view that's under contentView moves up during the animation:
CGRect frame = view.frame;
frame.size.height = 0;
[[view superview] layoutIfNeeded];
[UIView animateWithDuration:0.5 animations:^{
view.frame = frame;
[[view superview] layoutIfNeeded];
}];
I also had to change some properties for the subviews to have them animate correctly (this step might not be needed. It depends to the base layout of the subviews):
for UITextView: scrollEnabled = YES
for UIButton: clipsToBounds = YES
At the end of the animation, on the completion block, I set contentView.translatesAutoresizingMaskIntoConstraints back to NO and restored the subviews properties:
for UITextView: scrollEnabled = NO
for UIButton: clipsToBounds = NO
(I ended up using UITextViews instead of UILabels because I needed the text to be selectable. I then set scrollEnabled to NO to size the textviews based on their content).
Yes: add a height constraint with a constant of 0.
You can use deactivateConstraints() to temporarily disable other constraints that might conflict with this, and activateConstraints() to re-enable them.
You could put the relevant constraints in an array programmatically, or use an outlet collection if you're using a storyboard.

AutoLayout not working after CGRectOffset

I have a UIView called containerView.
I add this as a subview to a controller view's root view. I have programmatically added a few constraints to it (I centered it and made the width a few points from the superview's width).
I have added a few UILabels to the containerView as subviews. The height of the UILabels dictate the height of the containerView.
When the user taps the screen, the containerView is moved up from CGRectOffset() and once the animation is complete, it is moved back to the original position.
CGPoint absolutePoint = self.containterView.frame.origin;
self.containerYConstraint.constant = -absolutePoint;
[UIView
animateWithDuration:0.5
animations:^
{
[viewForUpdate setNeedsUpdateConstraints];
}
completion:^(BOOL finished)
{
self.containerYConstraint.constant = 0;
[viewForUpdate setNeedsUpdateConstraints];
[self.containerView.subviews makeObjectsPerformSelector:#selector(removeFromSuperview)]; // Remove all subviews
}];
I need to remove the UILabels I have put in there as subviews and replace them with different labels. However, the moment I remove one of the UILabel's the entire containerView goes missing (I set the background as red so I can see it). I remove all the subviews in the example but when I try to just remove one the same effect occurs.
Why does this occur? Does this have something to do with auto layout? Also if I want to recenter it after I remove one of the UILabels, how do I re-do the constraints?
Modifying frames when using AutoLayout is a no-no. Once you begin using AutoLayout you're effectively telling the system that you want it to set the frames for you.
Instead of animating the frame directly, create properties pointing to your constraints and animate those constraints.

Autolayout Animation Keeping UILabel Centered

I'm writing an iOS 7 app and I have a rectangle with a label on it. The label is aligned center and is created to be the same size as the view (for simplicity when increasing the size).
The desired effect is when clicked, to animate the view to full size, with the label staying centered the whole time.
I currently have tried:
Setting the top, left, bottom, and right constraints on the label to 0
Setting the height and width of the label to the view at the initial size and animating it to the full size along with the view animation
Setting the label's top and left constraints to 0 and animating the size to the full size
None of these produce the desired output. Each time the label seems to just snap to its final size before the view even starts animating.
Here is my code:
_viewHeightConstraint.constant = self.view.frame.size.height;
_viewWidthConstraint.constant = self.view.frame.size.width;
_viewTopConstraint.constant = 0;
_viewLeftConstraint.constant = 0;
_labelWidthConstraint.constant = self.view.frame.size.width;
_labelHeightConstraint.constant = self.view.frame.size.height;
[self.view needsUpdateConstraints];
[UIView animateWithDuration:1.5f
animations:^(void) {
[self.myView layoutIfNeeded]; //perform relayout of view containing label before relayout of entire view
[self.view layoutIfNeeded];
}];
I'm not sure if I've provided everything necessary, as I'm still new to autolayout. However, the desired effect is a view that animates to full size while the label in the center stays centered.
Add these constraints to your label:
(UIViewAutoresizingFlexibleLeftMargin |
UIViewAutoresizingFlexibleRightMargin |
UIViewAutoresizingFlexibleTopMargin |
UIViewAutoresizingFlexibleBottomMargin)
Why do you call needsUpdateConstraints?
All the changes occur there. Just call:
...
_labelWidthConstraint.constant = self.view.frame.size.width;
_labelHeightConstraint.constant = self.view.frame.size.height;
[UIView animateWithDuration:1.5f
animations:^(void) {
[self.view layoutIfNeeded];
}];
On a side note, why do you set height and width constraints on the label? You can just add center horizontal and vertical constraints. I don't think you can set vertical text alignment for a label. But that was not your initial issue.

Resources