Animating Constraint Changes, position animates but height jumps without animation - ios

I have a custom UIView. Within that UIView I have a UILabel with a yellow background containing #"8" called labelWhite. I programmatically create 4 constraints for that UILabel: top, left, width, and height.
When I tap the UIView, I change the constant value of the height and animate the layout over 5 seconds. However, the height of the view immediately jumps to the new value, but the y position also changes and jumps to a new value immediately. Then over the 5 second animation, the y position animates back to where it should have stayed all along.
You can see a video of it happening here: http://inadaydevelopment.com/stackoverflow/Constraints0.mov
What it SHOULD do is just remain at y=0 and shrink vertically. What am I doing wrong?
EDIT:
I just discovered that my animations work exactly as intended if my subviews are UIViews, but as UILabels I get the jump-size + animate-position.
What is going on? Is there a way I can get UILabels to animate their size?
This is the code I use to modify and animate the layout:
self.constraintWhiteHeight.constant = secondaryHeight;
[UIView animateWithDuration:5.0 animations:^{
[self layoutIfNeeded];
}];
This is the code I use to create the constraints:
// ------ Top -----
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:self
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.labelWhite
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:0];
[self addConstraint:constraint];
// ------ Left -----
self.constraintWhiteLeft = [NSLayoutConstraint constraintWithItem:self
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:self.labelWhite
attribute:NSLayoutAttributeLeft
multiplier:1.0
constant:0];
// ------ Width -----
NSString *constraintString = [NSString stringWithFormat:#"H:[_labelWhite(==%.0f)]", self.bounds.size.width];
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:constraintString
options:0
metrics:nil
views:NSDictionaryOfVariableBindings(_labelWhite)];
NSLog(#"constraints: %#", constraints);
self.constraintWhiteWidth = [constraints objectAtIndex:0];
[self.labelWhite addConstraints:constraints];
// ------ Height -----
constraintString = [NSString stringWithFormat:#"V:[_labelWhite(==%.0f)]", primaryHeight];
constraints = [NSLayoutConstraint constraintsWithVisualFormat:constraintString
options:0
metrics:nil
views:NSDictionaryOfVariableBindings(_labelWhite)];
self.constraintWhiteHeight = [constraints objectAtIndex:0];
[self.labelWhite addConstraints:constraints];

I don't think you're doing anything wrong, this seems to be the way that labels behave when you try to animate their height. I know I've wrestled with this problem in the past, and I can't remember if I've ever found a solution that works by animating the constraints in an animation block. The way I have gotten it to work is to use a timer or CADisplayLink to adjust the height constraint.
-(void)shrinkLabel {
self.constraintWhiteHeight.constant -= 1;
if (self.constraintWhiteHeight.constant >= secondaryHeight)
[self performSelector:#selector(shrinkLabel) withObject:nil afterDelay:.05];
}
After Edit:
Although the timer method works, it doesn't always look smooth, depending on the speed and increments you use. Another way to do this is to use a large UIView (the same size as the yellow label in your movie) with a smaller UILabel inside that has centerX and centerY constraints to the larger view. Then, animate the yellow view as usual with animateWithDuration:animations:
-(void)shrinkLabel {
self.yellowViewHeightCon.constant = secondaryHeight;
[UIView animateWithDuration:5 animations:^{
[self layoutIfNeeded];
}];
}

Related

Changing programmatic/VFL constraints on-the-fly

I'm manually adding a subview to a view and am positioning it with constraints...
UIView *superview = self.view;
UIView *subview = self.subview;
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(superview, subview);
[self.superview addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(0)-[subview]-(0)-|" options:NSLayoutFormatAlignAllBaseline metrics:nil views:viewsDictionary]];
[self.superview addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-(0)-[subview]-(0)-|" options:NSLayoutFormatAlignAllBaseline metrics:nil views:viewsDictionary]];
This positions my subview neatly in the superview. However, at a later time I would like to apply a margin around my subview (with animation) so that it is inset by 100. So in effect, my constraints in visual format language would be...
"H:|-(100)-[subview]-(100)-|"
"V:|-(100)-[subview]-(100)-|"
How can I attach variables to my 'margin' value so that I can transition between the two types of display for the subview?
[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(0)-[subview]-(0)-|" options:NSLayoutFormatAlignAllBaseline metrics:nil views:viewsDictionary]
Will return an array with the constraints in the same order that they were specified, you can look for it inside the array and then change its constant value.
But I think the easiest way is to create a reference to it like so (you can store it in an internal variable or property):
NSLayoutConstraint* space = [NSLayoutConstraint constraintWithItem:self.button1 attribute:NSLayoutAttributeRight
relatedBy:NSLayoutRelationEqual toItem:self.button2
attribute:NSLayoutAttributeLeft multiplier:1.0 constant:12.0];
and add it like this:
[self.view addConstraints:#[space]];
then you can change space.constant to some value. and animate it like so:
[UIView animateWithDuration:1.0 animations:^{
// Make all constraint changes here
[self.view layoutIfNeeded];}];
Other approach would be to remove all constraints and add VFL constraints with updated values, and then perform layoutIfNeeded inside the animation block as above.

Auto layout: changing a view's center x alignment programatically

I have an image view that is x centered to another label above it.
After tapping a button, I want that image view to be x centered to another label. I tried connecting the constraint to an outlet and replacing it with a new NSLayoutConstraint (since I cannot modify the Second Item) then calling layoutIfNeeded on the parent view, but it doesn't seem to change.
[self.tabsView layoutIfNeeded];
self.tabIndicatorCenterXAlignment = [NSLayoutConstraint
constraintWithItem:self.tabIndicatorIV
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:_tabsBtnArray[pageNumber]
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:0];
[UIView animateWithDuration:0.5 animations:
^{
[self.tabsView layoutIfNeeded];
}];

Auto Layout: animating changes to NSLayoutConstraint's multiplier

I have this simple view:
- (void)viewDidLoad
{
[super viewDidLoad];
redBox = [[UIView alloc] init];
[redBox setBackgroundColor:[UIColor redColor]];
[redBox setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.view addSubview:redBox];
//H:
widthConstraint = [NSLayoutConstraint constraintWithItem:redBox
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeWidth
multiplier:0.5
constant:0.0];
[self.view addConstraint:widthConstraint];
//More constraints...
}
I’d like to animate an increase in redBox's width.
I’ve tried this:
widthConstraint.constant = 100;
[UIView animateWithDuration:1
animations:^{
[self.view layoutIfNeeded];
}
];
this increases the view’s width by 100 and animates it, but I need to increase it by an amount proportional to its superview’s width. In other words, I’d like to set widthConstraint’s multiplier to 0.8.
But NSLayoutConstraint’s multiplier is readonly! Does this mean I have to remove, modify and re-add widthConstraint every time I want to animate a change to its multiplier or is there a better way?
Unfortunately, there is no better way. Removing and re-adding with a different multiplier is your only option. Please file a bug that you would like to see multiplier to be readwrite.
I asked this question before. And we are found only one way, it's remove constraints and added it later with modify multipler. #"It's Apple baby"(©My Boss)
UPD
look at Masonry mb you can replace multipler on plain constrain with constant.

Auto Layout - make a view's dimensions dependent on another view's

Simple deal: I'd like to make a UIView's width half of its superview's width. Here's my code:
//Display a red rectangle:
UIView *redBox = [[UIView alloc] init];
[redBox setBackgroundColor:[UIColor redColor]];
[redBox setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.view addSubview:redBox];
//Make its height equal to its superview's,
//minus standard spacing on top and bottom:
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-[redBox]-|" options:0 metrics:nil views:#{#"redBox": redBox}];
[self.view addConstraints:constraints];
//Make its width half of its superviews's width (the problem):
NSLayoutConstraint *constraint = [NSLayoutConstraint
constraintWithItem:redBox
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeWidth
multiplier:0.5
constant:0.0
];
[self.view addConstraint:constraint];
[self.view setBackgroundColor:[UIColor blueColor]];
This is what I get:
If I set multiplier to 1.0, then the view's width is half of its superview. But why is that?
The problem is probably that your red box view is under-constrained. You've given it a width, but nothing to tell it where it should position itself horizontally. Since it should be half the width of the superview, it could choose to position itself on the left half of the superview. Or the right half. Or the middle. You didn't specify, so the framework just gets to pick something.
In this case, it looks like it's choosing to align the center of the red box with the left edge of the superview. This seems like an odd choice, but again, you didn't specify anything. It can pick whatever it wants.
Add another constraint that will position the view horizontally. That should fix the problem.

ios7 auto layout button resize issue

I am using xcode 5 with storyboards and autolayout turned ON. My layouts of one of my ViewControllers has a UIButton with an image background. The view controller also has 3 buttons anchored to the bottom of the view and some labels above. I laid out my storyboards to a 4" retina display (ie: iphone 5+) and the issue I have is when simulating to a 3.5" display (ie: iphone 4, 4s, etc)
I have pinned the height of the anchored buttons at the bottom to remain constant. What I want is when the app runs on a 3.5" display iphone, that the UIButton with the image will resize smaller (keeping all other labels & buttons same size & spacing). So the aspect ratio would remain the same for that UIButton, it would just get smaller.
I haven't been able to find anything in the auto layout tutorial or others online about how to set up constraints to do this (where the height/width remain proportional).
I think what you really want is as follow:
1.Label's top is 100 from its superView and its bottom is 68 from its superView
2.In 4" display, its size is 200x400 with ratio .5
3.In 3.5" display, its size is 312x624 with ratio .5
- (void)viewDidLoad
{
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
// create search bar
CGRect frame = CGRectMake(60, 100, 200, 400);
_label = [[UILabel alloc] initWithFrame:frame];
_label.backgroundColor = [UIColor blueColor];
[self.view addSubview:_label];
// layout search bar
_label.translatesAutoresizingMaskIntoConstraints = NO;
// height
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:_label
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationLessThanOrEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:0
constant:400];
// width
[_label addConstraint:constraint];
constraint = [NSLayoutConstraint constraintWithItem:_label
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:_label
attribute:NSLayoutAttributeHeight
multiplier:0.5
constant:0];
[_label addConstraint:constraint];
// vertical
NSDictionary *views = NSDictionaryOfVariableBindings(_label, self.view);
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-100-[_label]-68-|"
options:0
metrics:nil
views:views];
[self.view addConstraints:constraints];
// horizontal
constraint = [NSLayoutConstraint constraintWithItem:_label
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterX
multiplier:1
constant:0];
[self.view addConstraint:constraint];
}
Checkout the 'Taking Control of Auto Layout in Xcode 5' video from the WWDC 2013 Videos -- Quickly: the top part of the Add New Constraints area is probably what your looking for
I guess preserving aspect ratio is NOT possible in auto layouts via interface builder. #fail.
According to this: http://www.raywenderlich.com/50319/beginning-auto-layout-tutorial-in-ios-7-part-2, at the very end of the tutorial.
So, i guess I need to do this programmatically somehow.

Resources