I want to perform two simultaneous movement animations. First animation on firstView starts immediately. Second animation on secondView, starts after a slight delay while the first animation is still running. secondView constraint is related to firstView. The code works perfectly well on iOS 8.
firstView and secondView are subviews of view
view
|--- firstView
|--- secondView
Code:
UIView *firstView = ...;
UIView *secondView = ...;
[UIView animateWithDuration:0.5 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:firstView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.topLayoutGuide attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0];
[self.view addConstraint:constraint];
[self.view layoutIfNeeded];
} completion:^(BOOL finished) {
}];
[UIView animateWithDuration:0.5 delay:0.15 options:UIViewAnimationOptionCurveEaseInOut animations:^{
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:secondView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:firstView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0];
[self.view addConstraint:constraint];
[self.view layoutIfNeeded];
} completion:^(BOOL finished) {
}];
On iOS 7, once the second layoutIfNeeded is called, the first animations stops and only the seconds animation animates.
Any suggestions?
Answering my own question.
I ended up doing the animation in a 2-step (3-step, depends how you count) solution. First add the constraints without calling layoutIfNeeded. Then update the firtView and secondView frames. Lastly call layoutIfNeeded in the completion block of the last animation.
Code:
UIView *firstView = ...;
UIView *secondView = ...;
/* first step */
NSLayoutConstraint *constraint1 = [NSLayoutConstraint constraintWithItem:firstView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.topLayoutGuide attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0];
[self.view addConstraint:constraint1];
NSLayoutConstraint *constraint2 = [NSLayoutConstraint constraintWithItem:secondView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:firstView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0];
[self.view addConstraint:constraint2];
/* second step */
[UIView animateWithDuration:0.5 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
CGRect firstViewFrame = CGRectMake(...); // set frame according to constaint1
firstView.frame = firstViewFrame;
}];
[UIView animateWithDuration:0.5 delay:0.15 options:UIViewAnimationOptionCurveEaseInOut animations:^{
CGRect secondViewFrame = CGRectMake(...); // set frame according to constaint2
secondView.frame = secondViewFrame;
} completion:^(BOOL finished) {
/* second and a half step */
[self.view setNeedsLayout];
[self.view layoutIfNeeded];
}];
Two important notes:
You need to call layoutIfNeeded within the animation block. Apple
actually recommends you call it once before the animation block to
ensure that all pending layout operations have been completed.
You need to call it specifically on the parent view (e.g.
self.view), not the child view that has the constraints attached to
it. Doing so will update all constrained views, including animating
other views that might be constrained to the view that you changed
the constraint of (e.g. View B is attached to the bottom of View A
and you just changed View A's top offset and you want View B to
animate with it).
You must do it like this:
[self.view layoutIfNeeded];
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:firstView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.topLayoutGuide attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0];
[self.view addConstraint:constraint];
[UIView animateWithDuration:0.5 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
[self.view layoutIfNeeded];
} completion:^(BOOL finished) {
}];
Related
I am trying to animate a UIView by updating its constraints. I want to initially place the view at the bottom of the screen (off screen) and then set the top constraint to 0 so that the animation shows the view moving upwards.
I have tried the following with no luck at all, the animation is incorrect.
CGRect screenBound = [[UIScreen mainScreen] bounds];
CGSize screenSize = screenBound.size;
//CGFloat screenWidth = screenSize.width;
CGFloat screenHeight = screenSize.height;
self.viewSearchWrapper.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.viewSearchWrapper];
NSLayoutConstraint *trailing = [NSLayoutConstraint constraintWithItem:self.viewSearchWrapper
attribute:NSLayoutAttributeTrailing
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeTrailing
multiplier:1.0f
constant:0.f];
// Leading
NSLayoutConstraint *leading = [NSLayoutConstraint
constraintWithItem:self.viewSearchWrapper
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeLeading
multiplier:1.0f
constant:0.f];
// Bottom
NSLayoutConstraint *bottom = [NSLayoutConstraint
constraintWithItem:self.viewSearchWrapper
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeBottom
multiplier:1.0f
constant:0.f];
// Bottom
NSLayoutConstraint *top = [NSLayoutConstraint
constraintWithItem:self.viewSearchWrapper
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeTop
multiplier:1.0f
constant:screenHeight];
[self.view addConstraint:trailing];
[self.view addConstraint:bottom];
[self.view addConstraint:leading];
[self.view addConstraint:top];
[self.viewSearchWrapper setNeedsUpdateConstraints];
[top setConstant:0.f];
[UIView animateWithDuration:1 animations:^{
[self.view layoutIfNeeded];
}];
Add layoutIfNeeded to first set the initial constraint to viewSearchWrapper.
// .... keep the code assigning the constraints as it is.
[self.view layoutIfNeeded]; // This will set the initial frame of viewSearchWrapper
[self.viewSearchWrapper setNeedsUpdateConstraints];
[top setConstant:0.f];
[UIView animateWithDuration:1 animations:^{
[self.view layoutIfNeeded];
}];
Add [top setConstant:0.f] inside animateWithDuration as shown below:
[UIView animateWithDuration:1 animations:^{
[top setConstant:0.f];
[self.view layoutIfNeeded];
}];
I have two views. Lets call them view1 and view2. They are at the same level in the views hierarchy. I want view2 to always follow view1 (both in position and size). I added some constraints to do that and it works fine. The problem is with the animations. Whenever I animate view1 I want view2 to do the same thing. Note that only view1 is visible to the 'outside world', and only view1 knows about view2 so I cannot just add view2 in the animations block. Can somehow view2 copy the animations of view1?
EDIT:
view1 is animated either by changing the frame or by changing constraints and view2 should work in both cases.
- (void)addView2 {
[self.superview insertSubview:self.view2 belowSubview:self];
self.view2.translatesAutoresizingMaskIntoConstraints = NO;
[self.superview addConstraint:[NSLayoutConstraint constraintWithItem:self.view2 attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0]];
[self.superview addConstraint:[NSLayoutConstraint constraintWithItem:self.view2 attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0]];
[self.superview addConstraint:[NSLayoutConstraint constraintWithItem:self.view2 attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeWidth multiplier:1.0 constant:0]];
[self.superview addConstraint:[NSLayoutConstraint constraintWithItem:self.view2 attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeHeight multiplier:1.0 constant:0]];
}
This is the relevant code from view1.
Animations:
[UIView animateWithDuration:kAnimationDuration animations:^{
view1.frame = some other frame;
}];
or
[UIView animateWithDuration:kAnimationDuration animations:^{
[view1 layoutIfNeeded]
}];
I dont know your animation code, I guess you are using autolayout so seems your animation act to the frame and not to the constraints involved. This can cause (to one or both view during animation) an ugly size distortion. Check your code and correct all animations by using constraints.
A simple example:
UIView.animateWithDuration(0.5, delay: 1.0, options: UIViewAnimationOptions.TransitionNone, animations: { () -> Void in
self.view1LeadingConstraint.constant = 30
}, completion: { (finished: Bool) -> Void in
})
Write [self.view layoutIfNeeded]; before animating any view :
for example,
[self.view layoutIfNeeded];
[UIView animateWithDuration:kAnimationDuration animations:^{
view1.frame = some other frame;
}];
You need to call layoutIfNeeded before animation it if you use autolayout and animating it by manipulating frames.
If it is not work then write layoutIfNeeded for other both view too and then animate
Hope this will help
I can't figured it out how to do slide in/ out animation with auto layout.
I add constraints like this:
NSDictionary *viewsDic = #{#"tmpView" : tmpView};
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[tmpView]|" options:0 metrics:nil views:viewsDic]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[tmpView]|" options:0 metrics:nil views:viewsDic]];
[UIView animateWithDuration:.4 animations:^{
[self.view layoutIfNeeded];
}];
but this animates to grow from 0,0 position. how to achieve slide in/out animation?
As far as I understand, you need to get shift-effect. If my assumption is right, you need to manage horizontal or vertical padding manually during animation. And you need to get the reference on horizontal/vertical constraint:
#property (nonatomic, strong) NSLayoutConstraint *topPadding;
#property (nonatomic, strong) NSLayoutConstraint *heightConstraint;
Adding a constraint with initial value so that tmpView is out of visible part of its superview:
self.heightConstraint = [NSLayoutConstraint constraintWithItem:tmpView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0
constant:0.0];
self.topPadding = [NSLayoutConstraint constraintWithItem:tmpView
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:self.view.bounds.size.height];
[self.view addConstraint:self.topPadding];
[self.view addConstraint:self.heightConstraint];
Then we need simply to show tmpView with animation:
[self.view layoutIfNeeded];
[UIView animateWithDuration:.4 animations:^{
self.topPadding.constant = 0.0;
self.heightConstraint.constant = self.view.bounds.size.height;
[self.view layoutIfNeeded];
}];
Whit this example tmpView appears from the bottom.
Please ensure that self.topPadding won't conflict with other you constraints
To hide the view you can use something like this:
[self.view layoutIfNeeded];
[UIView animateWithDuration:.4 animations:^{
self.topPadding.constant = self.superview.bounds.size.height;
[self.view layoutIfNeeded];
}];
UPD.: Sometimes you also need to add hight constraints as well. Added use of self.heightConstraint in my example
UPD.: You still need horizontal constraints.
Please leave these lines:
NSDictionary *viewsDic = #{#"tmpView" : tmpView};
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[tmpView]|" options:0 metrics:nil views:viewsDic]];
I have a View Controller containing a view (of another controller) and a tab bar at the bottom. I want to add an iAD banner such that when an ad is loaded and unloaded, the contained view is resized accordingly.
/* AutoLayout */
/* pin Left of child to left of parent */
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:child.view
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeLeft
multiplier:1.0
constant:0]];
/* pin Right of child to right of parent */
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:child.view
attribute:NSLayoutAttributeRight
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeRight
multiplier:1.0
constant:0]];
/* pin top of child to bottom of nav bar(or status bar if no nav bar) */
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:child.view
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.topLayoutGuide
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:0]];
/* pin Top of tab bar to bottom of child view */
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.bottomLayoutGuide
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:child.view
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:0]];
/*
* adConstraint is an instance variable to hold the constraint I want to animate
* Note that it is not an IBOutlet
*/
adConstraint = [NSLayoutConstraint constraintWithItem:child.view
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:0];
[self.view addConstraint:adConstraint];
In my delegate methods i have the following
- (void)bannerViewDidLoadAd:(ADBannerView *)banner
{
UIView animateWithDuration:1 animations:^{
adConstraint.constant = -banner.frame.size.height;
[self.view layoutIfNeeded];
}];
}
and
- (void)bannerView:(ADBannerView *)banner didFailToReceiveAdWithError:(NSError *)error
{
UIView animateWithDuration:1 animations:^{
adConstraint.constant = 0;
[self.view layoutIfNeeded];
}];
}
I seriously need help with this! Is it even possible to achieve this without Storyboard and the IBOutlet or am I doing something (terribly) wrong?
Please Help?!
*********************** EDIT: ************************
I've noticed a problem; The ad shows up but bannerDidLoadAd is not called. I know this because I added log statements in it. Does anyone know why this might be happening????
Try calling
[self.view setNeedsUpdateConstraints];
before
[self.view layoutIfNeeded];
It should work!
[UIView animateWithDuration:1 animations:^{
adConstraint.constant = 0;
[self.view setNeedsUpdateConstraints];
[self.view layoutIfNeeded];
}];
I have a simple animation where I'm expanding a new view from the center of the old one, while that one fades out. However, the subviews of this new view (a button and a label) "fly" in from off the lower right hand side of the screen as that new view expands to take up the whole screen. I've tried with and without autolayout turned on, and while the two scenarios give different results, they're both wrong.
The set up is simple, I have two unconnected view controllers in a storyboard, and use the following code to animate the view change:
-(void)switchViews2:(id)sender {
UIWindow *win = self.view.window;
YellowController *yellow = [self.storyboard instantiateViewControllerWithIdentifier:#"Yellow"];
yellow.view.frame = CGRectMake(0, 0, 1, 1);
yellow.view.center = self.view.center;
[win addSubview:yellow.view];
CGRect frame = self.view.frame;
[UIView animateWithDuration:5 animations:^{
yellow.view.frame = frame;
self.view.alpha = 0;
}
completion:^(BOOL finished) {
[self.view removeFromSuperview];
win.rootViewController = yellow;
}];
}
The question is, why don't the subviews stay where they're supposed to inside their superview as it animates.
To grow a subview into place, particularly when using Autolayout, it is far simpler to animate the transform property instead of the frame. This way, the subview's bounds property does not change, so it does not feel the need to relay out its subviews all the time during the animation.
Add your subview with its final frame, set its transform to be a scaling affine transform of, say, 0.1, then animate it to the identity transform. It will grow out from the center point, with all subviews in the correct position.
The problem was with the layout constraints. If instead of setting the view frame in the animation block, I add constraints between the window and the new view, then call layoutIfNeeded in the animation block it works correctly:
-(void)switchViews2:(id)sender {
UIWindow *win = self.view.window;
YellowController *yellow = [self.storyboard instantiateViewControllerWithIdentifier:#"Yellow"];
[yellow.view setTranslatesAutoresizingMaskIntoConstraints:NO];
yellow.view.frame = CGRectMake(0, 0, 1, 1);
yellow.view.center = self.view.center;
[win addSubview:yellow.view];
NSLayoutConstraint *con1 = [NSLayoutConstraint constraintWithItem:yellow.view attribute:NSLayoutAttributeLeading relatedBy:0 toItem:win attribute:NSLayoutAttributeLeading multiplier:1 constant:0];
NSLayoutConstraint *con2 = [NSLayoutConstraint constraintWithItem:yellow.view attribute:NSLayoutAttributeTop relatedBy:0 toItem:win attribute:NSLayoutAttributeTop multiplier:1 constant:20];
NSLayoutConstraint *con3 = [NSLayoutConstraint constraintWithItem:yellow.view attribute:NSLayoutAttributeTrailing relatedBy:0 toItem:win attribute:NSLayoutAttributeTrailing multiplier:1 constant:0];
NSLayoutConstraint *con4 = [NSLayoutConstraint constraintWithItem:yellow.view attribute:NSLayoutAttributeBottom relatedBy:0 toItem:win attribute:NSLayoutAttributeBottom multiplier:1 constant:0];
[win addConstraints:#[con1,con2,con3,con4]];
[UIView animateWithDuration:1 animations:^{
[win layoutIfNeeded];
self.view.alpha = 0;
}
completion:^(BOOL finished) {
[self.view removeFromSuperview];
win.rootViewController = yellow;
}];
}