Reverse a UIView Animation [duplicate] - ios

I have a problem with the setting UIViewAnimationOptionAutoReverse.
Here is my code.
CALayer *aniLayer = act.viewToChange.layer;
[UIView animateWithDuration:2.0 delay:1.0 options:(UIViewAnimationCurveLinear | UIViewAnimationOptionAutoreverse) animations:^{
viewToAnimate.frame = GCRectMake(1,1,100,100);
[aniLayer setValue:[NSNumber numberWithFloat:degreesToRadians(34)] forKeyPath:#"transform.rotation"];
} completion:nil ];
The problem is that, after the animation has reversed, the view jumps back to the frame set in the animation block. I want the view to grow and "ungrow" and stop at its original position.
Is there a solution without programming two consecutive animations?

You have three options.
When you use the -[UIView animateWithDuration:…] methods, the changes you make in the animations block are applied immediately to the views in question. However, there is also an implicit CAAnimation applied to the view that animates from the old value to the new value. When a CAAnimation is active on a view, it changes the displayed view, but does not change the actual properties of the view.
For example, if you do this:
NSLog(#"old center: %#", NSStringFromCGPoint(someView.center));
[UIView animateWithDuration:2.0 animations: ^{ someView.center = newPoint; }];
NSLog(#"new center: %#", NSStringFromCGPoint(someView.center));
you will see that 'old center' and 'new center' are different; new center will immediately reflect the values of newPoint. However, the CAAnimation that was implicitly created will cause the view to still be displayed at the old center and smoothly move its way to the new center. When the animation finishes, it is removed from the view and you switch back to just seeing the actual model values.
When you pass UIViewAnimationOptionAutoreverse, it affects the implicitly created CAAnimation, but does NOT affect the actual change you're making to the values. That is, if our example above had the UIViewAnimationOptionAutoreverse defined, then the implicitly created CAAnimation would animate from oldCenter to newCenter and back. The animation would then be removed, and we'd switch back to seeing the values we set… which is still at the new position.
As I said, there are three ways to deal with this. The first is to add a completion block on the animation to reverse it, like so:
First Option
CGPoint oldCenter = someView.center;
[UIView animateWithDuration:2.0
animations: ^{ someView.center = newPoint; }
completion:
^(BOOL finished) {
[UIView animateWithDuration:2.0
animations:^{ someView.center = oldCenter; }];
}];
Second Option
The second option is to autoreverse the animation like you're doing, and set the view back to its original position in a completion block:
CGPoint oldCenter = someView.center;
[UIView animateWithDuration:2.0
delay:0
options: UIViewAnimationOptionAutoreverse
animations: ^{ someView.center = newPoint; }
completion: ^(BOOL finished) { someView.center = oldCenter; }];
However, this may cause flickering between the time that the animation autoreverse completes and when the completion block runs, so it's probably not your best choice.
Third Option
The last option is to simply create a CAAnimation directly. When you don't actually want to change the final value of the property you're changing, this is often simpler.
#import <QuartzCore/QuartzCore.h>
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"position"];
animation.autoreverses = YES;
animation.repeatCount = 1; // Play it just once, and then reverse it
animation.toValue = [NSValue valueWithCGPoint:newPoint];
[someView.layer addAnimation:animation forKey:nil];
Note that the CAAnimation way of doing things never changes the actual values of the view; it just masks the actual values with an animation. The view still thinks it's in the original location. (This means, for example, that if your view responds to touch events, it will still be watching for those touch events to happen at the original location. The animation only changes the way the view draws; nothing else.
The CAAnimation way also requires that you add it to the view's underlying CALayer. If this scares you, feel free to use the -[UIView animateWithDuration:…] methods instead. There's additional functionality available by using CAAnimation, but if it's something you're not familiar with, chaining UIView animations or resetting it in the completion block is perfectly acceptable. In fact, that's one of the main purposes of the completion block.
So, there you go. Three different ways to reverse an animation and keep the original value. Enjoy!

Here's my solution. For 2x repeat, animate 1.5x and do the last 0.5x part by yourself:
[UIView animateWithDuration:.3
delay:.0f
options:(UIViewAnimationOptionRepeat|
UIViewAnimationOptionAutoreverse)
animations:^{
[UIView setAnimationRepeatCount:1.5f];
... animate here ...
} completion:^(BOOL finished) {
[UIView animateWithDuration:.3 animations:^{
... finish the animation here ....
}];
}];
No flashing, works nice.

There is a repeatCount property on CAMediaTiming. I think you have to create an explicit CAAnimation object, and configure it properly. The repeatCount for grow and ungrow would be 2, but you can test this.
Something a long the lines of this. You would need two CAAnimation objects though, one for the frame and one for the rotation.
CABasicAnimation *theAnimation;
theAnimation=[CABasicAnimation animationWithKeyPath:#"transform.rotation"];
theAnimation.duration=3.0;
theAnimation.repeatCount=1;
theAnimation.autoreverses=YES;
theAnimation.fromValue=[NSNumber numberWithFloat:0.0];
theAnimation.toValue=[NSNumber numberWithFloat:degreesToRadians(34)];
[theLayer addAnimation:theAnimation forKey:#"animateRotation"];
AFAIK there is no way to avoid programming two animation objects.

Here is a Swift version of #Rudolf Adamkovič's answer, where you set the animation to run 1.5 times and in the completion block run a 'reverse' animation one final time.
In the below snippet I am translating my view by -10.0 pts on the y-axis 1.5 times. Then in the completion block I animate my view one final time back to its original y-axis position of 0.0.
UIView.animate(withDuration: 0.5, delay: 0.5, options: [.repeat, .autoreverse], animations: {
UIView.setAnimationRepeatCount(1.5)
self.myView.transform = CGAffineTransform(translationX: 0.0, y: -10.0)
}, completion: { finished in
UIView.animate(withDuration: 0.5, delay: 0, options: [], animations: {
self.myView.transform = CGAffineTransform(translationX: 0.0, y: 0.0)
}, completion: nil)
})

Related

box flip 3D animation for side menu

I have scrollview and inside scrollview I have 2 views as MainView and another as SideMenuView.
What I want to make animation like below.
Any idea what needs to be done to get this working?
Psuedo Code below:
- (void)animateSideMenu{
homeView.frame = CGRectMake(sideMenuWidth, 0.0, (self.view.frame.size.width - sideMenuWidth), self.view.frame.size.height);
[UIView animateWithDuration:1.0 delay:0.0
options:UIViewAnimationOptionCurveLinear
animations:^{
sideMenu.frame = CGRectMake(0.0, 0.0, sideMenuWidth, sideMenuHeight);
[self flipAnimation];
} completion:^(BOOL finished) {
}];
}
- (void)flipAnimation{
CABasicAnimation *yRotate = [CABasicAnimation animationWithKeyPath:#"transform.rotation.y"];
yRotate.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeRotation(M_PI_2, 0, 1, 0)];
yRotate.toValue = #(M_PI * 1.5);
yRotate.duration = 0.5;
yRotate.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
[sideMenu.layer addAnimation:yRotate forKey:#"yRotate"];
}
Below are the steps to develop this type of animation:
set the frame of homeView(Red colour view) of screen size & add pan gesture to this view.
set frame of sideMenu view in negative x-axis.
Create a responder function for pan gesture recogniser, in this function call the above mentioned animateSideMenu function.
Adjust the animation parameters accordingly.
Try with this & let me know if anything comes up.
I would not use a scrollview but rather a UIPanGestureRecognizer in which I would detect your current "pan offset" and calculate a current fraction of the animation and positions of those two views (let's simplify it so that the menu is view1 and the rest view2).
Then I would simply set an appropriate transform in the .Changed state.
In .Ended state I would see whether the menu is closer to being open or close and create an animation with .toValue set accordingly. You should use CAAnimations or better - Facebook pop animations because you can pause and remove them and the views stay put and not jump to their initial positions (as is often the case with UIViewAnimate).
If you need help with writing exact code you can write here and I'll edit my answer.
There's tutorial for exactly that menu, though it's in Swift:
https://www.raywenderlich.com/87268/3d-effect-taasky-swift

How to properly animate UIScrollView contentOffset

I have UIScrollView subclass. Its content is reusable - about 4 or 5 views are used to display hundreds of elements (while scrolling hidden objects reused and jumps to another position when its needed to see them)
What i need: ability to automatically scroll my scroll view to any position. For example my scroll view displays 4th, 5th and 6th element and when I tap some button it needs to scroll to 30th element. In other words I need standard behaviour of UIScrollView.
This works fine:
[self setContentOffset:CGPointMake(index*elementWidth, 0) animated:YES];
but I need some customisation. For example, change animation duration, add some code to perform on end of animation.
Obvious decision:
[UIView animateWithDuration:3 delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
[self setContentOffset:CGPointMake(index*elementWidth, 0)];
} completion:^(BOOL finished) {
//some code
}];
but I have some actions connected to scroll event, and so now all of them are in animation block and it causes all subview's frames to animate too (thanks to few reusable elements all of them animates not how i want)
The question is: How can I make custom animation (in fact I need custom duration, actions on end and BeginFromCurrentState option) for content offset WITHOUT animating all the code, connected to scrollViewDidScroll event?
UPD:
Thanks to Andrew's answer(first part) I solved issue with animation inside scrollViewDidScroll:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
[UIView performWithoutAnimation:^{
[self refreshTiles];
}];
}
But scrollViewDidScroll must (for my purposes) executes every frame of animation like it was in case of
[self setContentOffset:CGPointMake(index*elementWidth, 0) animated:YES];
However, now it executes only once at start of animation.
How can I solve this?
Did you try the same approach, but with disabled animation in scrollViewDidScroll ?
On iOS 7, you could try wrapping your code in scrollViewDidScroll in
[UIView performWithoutAnimation:^{
//Your code here
}];
on previous iOS versions, you could try:
[CATransaction begin];
[CATransaction setDisableActions:YES];
//Your code here
[CATransaction commit];
Update:
Unfortunately that's where you hit the tough part of the whole thing. setContentOffset: calls the delegate just once, it's equivalent to setContentOffset:animated:NO, which again calls it just once.
setContentOffset:animated:YES calls the delegate as the animation changes the bounds of the scrollview and you want that, but you don't want the provided animation, so the only way around this that I can come up with is to gradually change the contentOffset of the scrollview, so that the animation system doesn't just jump to the final value, as is the case at the moment.
To do that you can look at keyframe animations, like so for iOS 7:
[UIView animateKeyframesWithDuration:duration delay:delay options:options animations:^{
[UIView addKeyframeWithRelativeStartTime:0.0 relativeDuration:0.5 animations:^{
[self setContentOffset:CGPointMake(floorf(index/2) * elementWidth, 0)];
}];
[UIView addKeyframeWithRelativeStartTime:0.5 relativeDuration:0.5 animations:^{
[self setContentOffset:CGPointMake(index*elementWidth, 0)];
}];
} completion:^(BOOL finished) {
//Completion Block
}];
This will get you two updates and of course you could use some math and a loop to add up a lot more of these with the appropriate timings.
On previous iOS versions, you'll have to drop to CoreAnimation for keyframe animations, but it's basically the same thing with a bit different syntax.
Method 2:
You can try polling the presentationLayer of the scrollview for any changes with a timer that you start at the beginning of the animation, since unfortunately the presentationLayer's properties aren't KVO observable. Or you can use needsDisplayForKey in a subclass of the layer to get notified when the bounds change, but that'll require some work to set up and it does cause redrawing, which might affect performance.
Method 3:
Would be to dissect exactly what happens to the scrollView when animated is YES try and intercept the animation that gets set on the scrollview and change its parameters, but since this would be the most hacky, breakable due to Apple's changes and trickiest method, I won't go into it.
A nice way to do this is with the AnimationEngine library. It's a very small library: six files, with three more if you want damped spring behavior.
Behind the scenes it uses a CADisplayLink to run your animation block once every frame. You get a clean block-based syntax that's easy to use, and a bunch of interpolation and easing functions that save you time.
To animate contentOffset:
startOffset = scrollView.contentOffset;
endOffset = ..
// Constant speed looks good...
const CGFloat kTimelineAnimationSpeed = 300;
CGFloat timelineAnimationDuration = fabs(deltaToDesiredX) / kTimelineAnimationSpeed;
[INTUAnimationEngine animateWithDuration:timelineAnimationDuration
delay:0
easing:INTULinear
animations:^(CGFloat progress) {
self.videoTimelineView.contentOffset =
INTUInterpolateCGPoint(startOffset, endOffset, progress);
}
completion:^(BOOL finished) {
autoscrollEnabled = YES;
}];
Try this:
UIView.animate(withDuration: 0.6, animations: {
self.view.collectionView.contentOffset = newOffset
self.view.layoutIfNeeded()
}, completion: nil)

How to stop and reverse a UIView animation?

I have animated a UIView so that it shrinks when the user touches a toggle button and it expands back to its original size when the user touches the button again. So far everything works just fine. The problem is that the animation takes some time - e.g. 3 seconds. During that time I still want the user to be able to interact with the interface. So when the user touches the button again while the animation is still in progress the animation is supposed to stop right where it is and reverse.
In the Apple Q&As I have found a way to pause all animations immediately:
https://developer.apple.com/library/ios/#qa/qa2009/qa1673.html
But I do not see a way to reverse the animation from here (and omit the rest of the initial animation). How do I accomplish this?
- (IBAction)toggleMeter:(id)sender {
if (self.myView.hidden) {
self.myView.hidden = NO;
[UIView animateWithDuration:3 animations:^{
self.myView.transform = expandMatrix;
} completion:nil];
} else {
[UIView animateWithDuration:3 animations:^{
self.myView.transform = shrinkMatrix;
} completion:^(BOOL finished) {
self.myView.hidden = YES;
}];
}
}
In addition to the below (in which we grab the current state from the presentation layer, stop the animation, reset the current state from the saved presentation layer, and initiate the new animation), there is a much easier solution.
If doing block-based animations, if you want to stop an animation and launch a new animation in iOS versions prior to 8.0, you can simply use the UIViewAnimationOptionBeginFromCurrentState option. (Effective in iOS 8, the default behavior is to not only start from the current state, but to do so in a manner that reflects both the current location as well as the current velocity, rendering it largely unnecessary to worry about this issue at all. See WWDC 2014 video Building Interruptible and Responsive Interactions for more information.)
[UIView animateWithDuration:3.0
delay:0.0
options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionAllowUserInteraction
animations:^{
// specify the new `frame`, `transform`, etc. here
}
completion:NULL];
You can achieve this by stopping the current animation and starting the new animation from where the current one left off. You can do this with Quartz 2D:
Add QuartzCore.framework to your project if you haven't already. (In contemporary versions of Xcode, it is often unnecessary to explicitly do this as it is automatically linked to the project.)
Import the necessary header if you haven't already (again, not needed in contemporary versions of Xcode):
#import <QuartzCore/QuartzCore.h>
Have your code stop the existing animation:
[self.subview.layer removeAllAnimations];
Get a reference to the current presentation layer (i.e. the state of the view as it is precisely at this moment):
CALayer *currentLayer = self.subview.layer.presentationLayer;
Reset the transform (or frame or whatever) according to the current value in the presentationLayer:
self.subview.layer.transform = currentLayer.transform;
Now animate from that transform (or frame or whatever) to the new value:
[UIView animateWithDuration:1.0
delay:0.0
options:UIViewAnimationOptionAllowUserInteraction
animations:^{
self.subview.layer.transform = newTransform;
}
completion:NULL];
Putting that all together, here is a routine that toggles my transform scale from 2.0x to identify and back:
- (IBAction)didTouchUpInsideAnimateButton:(id)sender
{
CALayer *currentLayer = self.subview.layer.presentationLayer;
[self.subview.layer removeAllAnimations];
self.subview.layer.transform = currentLayer.transform;
CATransform3D newTransform;
self.large = !self.large;
if (self.large)
newTransform = CATransform3DMakeScale(2.0, 2.0, 1.0);
else
newTransform = CATransform3DIdentity;
[UIView animateWithDuration:1.0
delay:0.0
options:UIViewAnimationOptionAllowUserInteraction
animations:^{
self.subview.layer.transform = newTransform;
}
completion:NULL];
}
Or if you wanted to toggle frame sizes from 100x100 to 200x200 and back:
- (IBAction)didTouchUpInsideAnimateButton:(id)sender
{
CALayer *currentLayer = self.subview.layer.presentationLayer;
[self.subview.layer removeAllAnimations];
CGRect newFrame = currentLayer.frame;
self.subview.frame = currentLayer.frame;
self.large = !self.large;
if (self.large)
newFrame.size = CGSizeMake(200.0, 200.0);
else
newFrame.size = CGSizeMake(100.0, 100.0);
[UIView animateWithDuration:1.0
delay:0.0
options:UIViewAnimationOptionAllowUserInteraction
animations:^{
self.subview.frame = newFrame;
}
completion:NULL];
}
By the way, while it generally doesn't really matter for really quick animations, for slow animations like yours, you might want to set the duration of the reversing animation to be the same as how far you've progressed in your current animation (e.g., if you're 0.5 seconds into a 3.0 second animation, when you reverse, you probably don't want to take 3.0 seconds to reverse that small portion of the animation that you have done so far, but rather just 0.5 seconds). Thus, that might look like:
- (IBAction)didTouchUpInsideAnimateButton:(id)sender
{
CFTimeInterval duration = kAnimationDuration; // default the duration to some constant
CFTimeInterval currentMediaTime = CACurrentMediaTime(); // get the current media time
static CFTimeInterval lastAnimationStart = 0.0; // media time of last animation (zero the first time)
// if we previously animated, then calculate how far along in the previous animation we were
// and we'll use that for the duration of the reversing animation; if larger than
// kAnimationDuration that means the prior animation was done, so we'll just use
// kAnimationDuration for the length of this animation
if (lastAnimationStart)
duration = MIN(kAnimationDuration, (currentMediaTime - lastAnimationStart));
// save our media time for future reference (i.e. future invocations of this routine)
lastAnimationStart = currentMediaTime;
// if you want the animations to stay relative the same speed if reversing an ongoing
// reversal, you can backdate the lastAnimationStart to what the lastAnimationStart
// would have been if it was a full animation; if you don't do this, if you repeatedly
// reverse a reversal that is still in progress, they'll incrementally speed up.
if (duration < kAnimationDuration)
lastAnimationStart -= (kAnimationDuration - duration);
// grab the state of the layer as it is right now
CALayer *currentLayer = self.subview.layer.presentationLayer;
// cancel any animations in progress
[self.subview.layer removeAllAnimations];
// set the transform to be as it is now, possibly in the middle of an animation
self.subview.layer.transform = currentLayer.transform;
// toggle our flag as to whether we're looking at large view or not
self.large = !self.large;
// set the transform based upon the state of the `large` boolean
CATransform3D newTransform;
if (self.large)
newTransform = CATransform3DMakeScale(2.0, 2.0, 1.0);
else
newTransform = CATransform3DIdentity;
// now animate to our new setting
[UIView animateWithDuration:duration
delay:0.0
options:UIViewAnimationOptionAllowUserInteraction
animations:^{
self.subview.layer.transform = newTransform;
}
completion:NULL];
}
There is a common trick you can use to do this, but it is necessary to write a separate method to shrink (and another similar one to expand):
- (void) shrink {
[UIView animateWithDuration:0.3
animations:^{
self.myView.transform = shrinkALittleBitMatrix;
}
completion:^(BOOL finished){
if (continueShrinking && size>0) {
size=size-1;
[self shrink];
}
}];
}
So now, the trick is to break the 3 seconds animation of shrinking into 10 animations (or more than 10, of course) of 0.3 sec each in which you shrink 1/10th of the whole animation: shrinkALittleBitMatrix. After each animation is finished you call the same method only when the bool ivar continueShrinking is true and when the int ivar size is positive (the view in full size would be size=10 and the view with minimum size would be size=0). When you press the button you change the ivar continueShrinking to FALSE, and then call expand. This will stop the animation in less than 0.3 seconds.
Well, you have to fill the details but I hope it helps.
First: how to remove or cancel a animation with view?
[view.layer removeAllAnimations]
if the view have many animations, such as, one animation is move from top to bottom, other is move from left to right;
you can cancel or remove a special animation like this:
[view.layer removeAnimationForKey:#"someKey"];
// the key is you assign when you create a animation
CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:#"someKey"];
when you do that, animation will stop, it will invoke it's delegate:
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
if flag == 1, indicate animation is completed.
if flag == 0, indicate animation is not completed, it maybe cancelled、removed.
Second: so , you can do what you want to do in this delegate method.
if you want get the view's frame when the remove code excute, you can do this:
currentFrame = view.layer.presentationlayer.frame;
Note:
when you get the current frame and remove animation , the view will also animate a period time, so currentFrame is not the last frame in the device screen.
I cann't resolve this question at now. if some day I can, I will update this question.

UIView animation jumps at beginning

I'm running this code...
[UIView animateWithDuration:0.2
delay:0.0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
CGAffineTransform settingsTransform = CGAffineTransformMakeTranslation(self.settingsView.frame.size.width, 0);
CGAffineTransform speedTransform = CGAffineTransformMakeTranslation(-self.speedView.frame.size.width, 0);
self.settingsView.transform = settingsTransform;
self.speedView.transform = speedTransform;
} completion:nil];
But when it runs the views jump half the transform in the opposite direction before sliding to half a position in the correct direction.
I've slowed down the animation duration to 5 seconds but the initial jump is instantaneous and half the transformation in the wrong direction.
When I animate back using this code...
[UIView animateWithDuration:0.2
delay:0.0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
self.settingsView.transform = CGAffineTransformIdentity;
self.speedView.transform = CGAffineTransformIdentity;
} completion:nil];
It does exactly the same thing.
The result is that the final movement is half the desired transform as it jumps half the transform in the wrong direction first.
I really can't work out why this is happening?
Any ideas.
::EDIT::
Clearing up some possible ambiguity.
I'm not trying to have these views "bounce" back to where they are. The views I'm animating are like control panel at the edge of the screen. When the user presses "go" the view then slide out of the way. When the user presses "stop" the panels slide back into the view.
At least they used to. Since enabling auto layout (which I need for other parts of the app) I can't just change the frame of the views so I went the the transform route.
You can see this effect by sticking a view into a view controller and a button to run the animation.
Thanks
I had the exact same problem so here is the solution I came up with.
CGAffineTransform transform = CGAffineTransformMake(1, 0, 0, 1, translation.x, translation.y);
[UIView animateWithDuration:0.25 animations:^{
_assetImageView.transform = transform;
[self.view layoutIfNeeded];
} completion:^(BOOL finished) {
}];
So I call [self.view layoutIfNeeded]; inside of the animate block. Without this the it has the same problem as you and jumps the distance negative translation first then animates to the correct position from there. I am calling this from a view controller so self is a sub class of UIViewController. In your case "self.view" may not exist but I hope you get the idea.
None of the solutions here worked for me... My animation would always skip. (except when it was right at the end of another animation, hint).
Some details:
The view that was doing this had a series of scale and translates already in the stack.
Solution:
Doing a keyframe animation, with a super short first key that would basically re apply the transform that that the last animation should have set.
UIView.animateKeyframesWithDuration(0.4, delay: 0.0, options: [.CalculationModeCubic], animations: { () -> Void in
//reset start point
UIView.addKeyframeWithRelativeStartTime(0.0, relativeDuration: 0.01, animations: { () -> Void in
self.element.transform = initialTransform
})
UIView.addKeyframeWithRelativeStartTime(0.01, relativeDuration: 0.99, animations: { () -> Void in
self.element.transform = finalTransform
})
}, completion: nil)
OK, having watched the WWDC videos again they state that one of the first things you have to do when using AutoLayout is to remove any calls for setFrame.
Due to the complexity of the screen I have removed the Auto-Layout completely and I'm now using the frame location to move the view around the screen.
Thanks
I've asked Apple Technical Support about this issue and got this response, so it's a bug.
iOS 8 is currently exhibiting a known bug in UIKit animations where
transform animations get the wrong fromValue (primarily affecting the
position of animated objects, making them appear to “jump”
unexpectedly at the start on an animation).
[...]
The workaround until a potential fix is delivered is to drop down to
Core Animation APIs to code your animations.
Here's improved version of Andreas answer
The key difference is that you can specify just one keyframe and get less code
UIView.animateKeyframes(withDuration: animationDuration, delay: 0.0, options: [], animations: {
UIView.addKeyframe(withRelativeStartTime: 0.0, relativeDuration: 1.0, animations: {
animatedView.transform = newTransform
})
}, completion: nil)
Does your settingsView or speedView already have a transformation applied before this animation takes place?
If the transformations for these views is not the identity transformation (aka CGAffineTransformIdentity, aka no transformation), you cannot access their .frame properties.
UIViews' frame properties are invalid when they have a transformation applied to them. Use "bounds" instead.
Since you want your second animation to occurs from the current state of your first animation (whether it is finished or not) I recommend to use the UIViewAnimationOptionLayoutSubviews option when setting your second animation.
[UIView animateWithDuration:0.2
delay:0.0
options:UIViewAnimationOptionCurveEaseOut|UIViewAnimationOptionLayoutSubviews
animations:^{
self.settingsView.transform = CGAffineTransformIdentity;
self.speedView.transform = CGAffineTransformIdentity;
} completion:nil];
Here is a sample of the code i used for testing by using the simple view controller template:
myviewcontroller.m:
- (void)viewDidLoad
{
[super viewDidLoad];
self.animatedView = [[UIView alloc] initWithFrame:CGRectMake(20, 20, 160, 80)];
self.animatedView.backgroundColor = [UIColor yellowColor];
[UIView animateWithDuration:0.2
delay:0.0
options:UIViewAnimationOptionCurveEaseOut|UIViewAnimationOptionLayoutSubviews
animations:^{
CGAffineTransform settingsTransform = CGAffineTransformMakeTranslation(self.animatedView.frame.size.width, 0);
self.animatedView.transform = settingsTransform;
}
completion:nil];
self.buttonToTest = [UIButton buttonWithType:UIButtonTypeRoundedRect];
self.buttonToTest.frame = CGRectMake(90, 20, 80, 40);
[self.buttonToTest setTitle:#"Click Me!" forState:UIControlStateNormal];
[self.buttonToTest addTarget:self action:#selector(buttonClicked) forControlEvents:UIControlEventTouchUpInside];
// set-up view hierarchy
[self.view addSubview:self.buttonToTest];
[self.view addSubview: self.animatedView];
}
- (void) buttonClicked
{
[UIView animateWithDuration:.2
delay:0.0
options:UIViewAnimationOptionCurveEaseOut|UIViewAnimationOptionBeginFromCurrentState|UIViewAnimationOptionLayoutSubviews
animations:^{
self.animatedView.transform = CGAffineTransformIdentity;
}
completion:nil];
}
I had this problem and solved it by:
Hide the original view
Add a temp view that copies the view you want to animate
Carry out animation which will now be as intended
Remove the temp view and unhide the original view with the final state
Hope this helps.
I tried the answers above, about the layoutIfNeeded, but it didn't work.
I added the layoutIfNeeded outside (before) the animation block and it solved my problem.
view.layoutIfNeeded()
UIView.animate(withDuration: duration, delay: 0, options: .beginFromCurrentState, animations: {
// Animation here
})

IOS: question about animation

I have this code:
successview is my view and it start with alpha 0.00 but when it finish the animation with autoreverse, successview become with alpha 1.00...why?
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:2];
[UIView setAnimationRepeatAutoreverses:YES];
[successView setAlpha:1.00];
[UIView commitAnimations];
One approach is to do two different animations: One that progresses towards an alpha value of 1.0 and then another that goes from 1.0 back to 0.
Use the animateWithDuration:animations:completion: method of the UIView to accomplish this. You can do the reverse in the completion block.
Something along the lines of:
[UIView animateWithDuration:0.2
animations:^{view.alpha = 1.0;}
completion:^(BOOL finished){
[UIView animateWithDuration:0.2
animations:^{view.alpha = 0;}];
}];
See UIView documentation for more details on animations.
It's in the docs:
If you combine autoreversing with a repeat count (settable using the setAnimationRepeatCount: method), you can create animations that shift back and forth between the old and new values a specified number of times. However, remember that the repeat count indicates the number of complete cycles. If you specify a whole number such as 2.0, the animation ends on the old value, which is followed by the view immediately updating itself to show the new value, which might be jarring. If you want the animation to end on the new value (instead of the old value), add 0.5 to the repeat count value. This adds an extra half cycle to the animation.
Update: read wrong your code, but docs suggest that you use animateWithDuration:delay:options:animations:completion: instead if you are targeting iOS 4.0 and later.
If someone is interested, here's Swift code:
UIView.animate(withDuration: 0.2, animations: {
view.alpha = 1
}) { (finished) in
UIView.animate(withDuration: 0.2, animations: {
view.alpha = 0
})
}

Resources