I have a set of nested UIView animations (2 or 3 levels deep at a given time) that I would like to be able to pause and resume. Some of these animations use -animateWithDuration:animations:completion: while others use -animateWithDuration:delay:options:animations:completion: in order to delay execution of the animation block.
I read and implemented Technical Q&A QA1673 about pausing all animations in a layer tree, but I'm encountering an issue with the animations that use a delay parameter. I can pause and resume animations just fine, but when the animation resumes, any animation block that has a delay associated with it appears to have its delay extended by the amount of time that the layer tree was paused. So for example, if one of the blocks has a delay of 1 second, and the layer tree was paused for 3 seconds, the animation delays for 4 seconds after resuming. I'm guessing this has something to do with the beginTime property? Any help would be appreciated.
// Pause and Resume methods, right from the technical Q&A
- (void)pauseAnimationsOnLayer:(CALayer *)layer
{
CFTimeInterval pausedTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil];
layer.speed = 0.0;
layer.timeOffset = pausedTime;
}
- (void)resumeAnimationsOnLayer:(CALayer *)layer
{
CFTimeInterval pausedTime = [layer timeOffset];
layer.speed = 1.0;
layer.timeOffset = 0.0;
layer.beginTime = 0;
CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
layer.beginTime = timeSincePause;
}
// Chained animations
- (void)animateNextPopup
{
[UIView animateWithDuration:kRFPVictorySequenceStatePopupDuration
animations:^{
[_currentStateImageView setHidden:NO];
[_currentStateImageView setTransform:CGAffineTransformIdentity];
}
completion:^(BOOL finished) {
[UIView animateWithDuration:kRFPVictorySequenceStateSlideOffDuration
delay:kRFPVictorySequenceStateVoteDelay
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
if (winnerIsDem) {
[_currentStateImageView setFrame:CGRectMake(-_currentStateImageView.frame.size.width,
_currentStateImageView.frame.origin.y,
_currentStateImageView.frame.size.width,
_currentStateImageView.frame.size.height)];
}
else {
[_currentStateImageView setFrame:CGRectMake(1024,
_currentStateImageView.frame.origin.y,
_currentStateImageView.frame.size.width,
_currentStateImageView.frame.size.height)];
}
}
completion:^(BOOL finished) {
// Do some stuff
}
];
}
];
}
I found the solution to the problem! You have to reset the self.layer.beginTime value to zero in the completion block of your animations.
e.g.
[UIView animateWithDuration:element.duration
delay:element.delay
options:UIViewAnimationOptionCurveLinear
animations:^{
// Animate properties here!
}
} completion:^(BOOL finished){
// Reset BeginTime all the time
// So, in case a pause took place the delay values are valid again!
**self.layer.beginTime = 0.0f;**
}];
The rest of the pause / resume code stays exactly the same.
Best!
I suggest a different approach.
Animation blocks are easy to implement, but useful only if you do not need any control over your animation.
Otherwise, you should use a timer and create your own animation manually.
[NSTimer scheduledTimerWithTimeInterval:0.1
target:self
selector:#selector(timerFired)
userInfo:nil
repeats:YES];
- (void)timerFired
{
if (isPaused) {
// Do nothing
} else {
// Animate
}
}
- (IBAction)pauseTapped:(id)sender
{
if (isPaused) {
isPaused = NO;
} else {
isPaused = YES;
}
}
isPaused is a flag that control your animation state.
Related
I have a UIImageView rotating forever on my screen... I use the code below, which I found HERE:
- (void) spinWithOptions: (UIViewAnimationOptions) options {
[UIView animateWithDuration: 0.0f
delay: 0.0f
options: options
animations: ^{
Hand.transform = CGAffineTransformRotate(Hand.transform, M_PI / 30);
}
completion: ^(BOOL finished) {
if (finished) {
if (animating) { //if, keep spinning
[self spinWithOptions: UIViewAnimationOptionCurveLinear];
} else if (options != UIViewAnimationOptionCurveEaseOut) { //final spin
//[self spinWithOptions: UIViewAnimationOptionCurveEaseOut];
}
}
}];
}
- (void) startSpin {
if (!animating) {
animating = YES;
[self spinWithOptions: UIViewAnimationOptionCurveEaseIn];
}
}
- (void) stopSpin {
animating = NO;
}
It worked find and the rotation seems smooth...
I've since added a countdown timer, using the follow:
-(void)timerTick:(NSTimer *)timer{
ticks -= 0.1; //NSLog(#"Total Ticks: %.2f",ticks);
if(ticks < 0.0){ //3 seconds is up
Failed.image = [UIImage imageNamed:#"Failed.png"];
ticks = 0.0;
[self GameOver]; //out of time...
}
else {
FailedBeforeTimer.image = [UIImage imageNamed:#"Failed Before.png"];
}
CountDownTimer.text = [NSString stringWithFormat:#"%.1f",ticks];
}
-(void)startTimer{
ticks = 3.0;
timer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(timerTick:) userInfo:nil repeats:YES];
}
-(void)stopTimer{
[timer invalidate];
ticks = 0.0;
}
Since adding this timer to my app, the rotation animation is really blocky and no longer a smooth rotation...
Any ideas what I should implement to solve this?
To check, I removed the timer and re-run the application, and sure enough, the animation was smooth again :(
UPDATE:
After some more trial and error, it seems to be this line of code that slows the process down: CountDownTimer.text = [NSString stringWithFormat:#"%.1f",ticks]; ie. When I don't display the countDownTimer text on the screen/update label on screen), the rotation is smooth still...
So confused... What can I do?
Animating with zero-duration animations does not make sense. That's not how UIView animation is supposed to work. My guess is that is the source of your problems. A zero duration animation will be jumpy by definition.
You should be animating your changes over some time-period. (Always use a non-zero duration.)
EDIT:
Note that your timerTick method is horribly inefficient. You load the same image into an image view on every tick. Don't do that. Set it up with the initial image, and then only change the image when your tick count reaches zero.
You should give CADisplayLink a try, it should be suitable for your case. It's easy to use, there are heaps of tutorials out there, just do a search.
I have a cycle animate in viewController
- (void)moveAnimating
{
[UIView animateWithDuration:2.0f animations:^{
_backgroundView.center = CGPointMake(self.center.x , self.center.y - kMoveDistanceHeight);
} completion:^(BOOL finished) {
if (_backgroundView.animating)
{
[_backgroundView moveAnimating];
}
}];
}
I want stop this animate When the viewController viewWillDisappear:
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
_backgroundView.animating = NO;
[_backgroundView.layer removeAllAnimations];
}
Because the animation is conflict with dismissViewcontrollerAnimation.
Question:
[_backgroundView.layer removeAllAnimations];
not work...
How to stop the animation?
Help me,thanks.
You are canceling animations correctly:
[_backgroundView.layer removeAllAnimations];
But you might forget about importing QuartzCore.h:
#import <QuartzCore/QuartzCore.h>
If it doesn't help try this:
[CATransaction begin];
[_backgroundView.layer removeAllAnimations];
[CATransaction commit];
If it doesn't help try to add this line to the code above:
[CATransaction flush];
Te solution from #KlimczakM didn't work for me.
I'm running an 'animateWithDuration' block that moves and image, and I use the next code to pause and resume the animation:
-(void)pauseLayer:(CALayer*)layer {
CFTimeInterval pausedTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil];
layer.speed = 0.0;
layer.timeOffset = pausedTime;
}
-(void)resumeLayer:(CALayer*)layer {
CFTimeInterval pausedTime = [layer timeOffset];
layer.speed = 1.0;
layer.timeOffset = 0.0;
layer.beginTime = 0.0;
CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
layer.beginTime = timeSincePause;
}
This is from the Apple Documentation at this link.
How to pause uiview animation and just leave it there?
//#define ANIMATION_WORM_OPTIONS (UIViewAnimationOptionCurveLinear|
// UIViewAnimationOptionBeginFromCurrentState)
- (IBAction)pressUp:(id)sender {
[self.game wormUp];
[self.gameScene.wormView.layer removeAllAnimations];
[UIView animateWithDuration:5 delay:0 options:ANIMATION_WORM_OPTIONS
animations:^{
[self.gameScene.wormView moveUp];
} completion:^(BOOL finished) {
}];
}
- (IBAction)pressRight:(id)sender {
[self.game wormRight];
[UIView animateWithDuration:5 delay:0 options:ANIMATION_WORM_OPTIONS
animations:^{
[self.gameScene.wormView moveRight];
} completion:^(BOOL finished) {
}];
}
For example here is what I am trying to do:
When I first tap "Right" a worm goes right.
Then when I tap "Up" during the worm goes right, the worm should stop and go up.
The problem in my code is the removeAllAnimations will first put the worm to the end of first animation end, then begin goes up.
You need to change the view's frame to the halting position frame. Just add the following before you do the removeAllAnimation on view.layer.
self.theView.frame = [self.theView.layer.presentationLayer frame];
CFTimeInterval pausedTime = [self.gameScene.wormView.layer convertTime:CACurrentMediaTime() fromLayer:nil];
self.gameScene.wormView.layer.speed = 0.0;
self.gameScene.wormView.layer.timeOffset = pausedTime;
I have a layer that I want to show when the user does an action, and hide after he is done. The layer is then shown again if the user does the action again.
In order for the UI to not go berserk with animations, I want:
that the fadeout animation only starts after one second (and is cancelled if the user does the action before it has started)
the the fadein animation to show the layer starts from the current fadeout opacity if the user is doing action before the layer has disappeared
what is the best way to do this?
I tried this, but this won't work properly (lot of noise with the layer blinking ):
- (void)hideHintLayer:(bool)hide
{
if(hide)
{
CABasicAnimation *animation = [CABasicAnimation animation];
animation.beginTime = CACurrentMediaTime() + 1.0f;
animation.duration = 1.0f;
animation.fillMode = kCAFillModeForwards;
animation.removedOnCompletion = NO;
animation.keyPath = #"opacity";
animation.fromValue = #(1.0f);
animation.toValue = #(0.0f);
[layer addAnimation:animation forKey:nil];
}
else
{
layer.opacity = 1.0f;
}
}
If you want to stop an animation, you can just do
[layer removeAllAnimations];
If you want to know the current alpha during the animated hiding of the view (so that you can reverse the animation, starting from the right place, you can do:
CALayer *presentationLayer = layer.presentationLayer;
CGFloat startingAlpha = presentationLayer.opacity;
You can then set the alpha to go from startingAlpha to 1.0 to animate the unhide without flickering the screen.
You can do the actual animations using block based animation, or I guess you could use CABasicAnimation, though I'm not sure why you would.
So, for example, you could do something like the following (in my example, I have a "show" button). I'm using block animations, but I suspect it would work fine for CABasicAnimation, too:
- (IBAction)onPressShowButton:(id)sender
{
[self showAndScheduleHide];
}
- (void)showAndScheduleHide
{
[UIView animateWithDuration:1.0
animations:^{
self.containerView.alpha = 1.0;
}
completion:^(BOOL finished) {
[self scheduleHide];
}];
}
- (void)show
{
[UIView animateWithDuration:1.0
animations:^{
self.containerView.alpha = 1.0;
}
completion:nil];
}
- (void)scheduleHide
{
self.timer = [NSTimer scheduledTimerWithTimeInterval:5.0
target:self
selector:#selector(startToHide)
userInfo:nil
repeats:NO];
}
- (void)startToHide
{
self.timer = nil;
self.hiding = YES;
[UIView animateWithDuration:5.0
delay:0.0
options:UIViewAnimationOptionAllowUserInteraction
animations:^{
self.containerView.alpha = 0.0;
}
completion:^(BOOL finished) {
self.hiding = NO;
}];
}
You can then have some utility method for reversing it or rescheduling a hide in progress:
- (void)reverseAndPauseHide
{
// if we have a "hide" scheduled, then cancel that
if (self.timer)
{
[self.timer invalidate];
self.timer = nil;
}
// if we have a hide in progress, then reverse it
if (self.hiding)
{
[self.containerView.layer removeAllAnimations];
CALayer *layer = self.containerView.layer.presentationLayer;
CGFloat currentAlpha = layer.opacity;
self.containerView.alpha = currentAlpha;
[self show];
}
}
Then, the question is when you know to call this reverseAndPauseHide and when to scheduleHide again. So, for example, you could handle touches:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];
[self reverseAndPauseHide];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesEnded:touches withEvent:event];
[self scheduleHide];
}
I'm doing animation for calayer using CAkeyframeanimation.Actually i`m moving the calayer using the position property by using the CAkeyframeanimation.I want to pause the calayer moving animation.When i tap the screen it should resumes from where it paused.
I added two animations for a calayer.One is for changing the bounds property of the calayer another one is position property.so i am using two animation for a single calayer but i want to pause the position property animation of the calayer util i tap the screen. After i tapped it should resumes from the paused position.
I used the following code but it stops all animations which i applied to the calayer.
- (void) pauseLayer: (CALayer *) theLayer
{
CFTimeInterval mediaTime = CACurrentMediaTime();
CFTimeInterval pausedTime = [theLayer convertTime: mediaTime fromLayer: nil];
theLayer.speed = 0.0;
theLayer.timeOffset = pausedTime;
}
- (void) removePauseForLayer: (CALayer *) theLayer;
{
theLayer.speed = 1.0;
theLayer.timeOffset = 0.0;
theLayer.beginTime = 0.0;
}
- (void) resumeLayer: (CALayer *) theLayer;
{
CFTimeInterval pausedTime = [theLayer timeOffset];
[self removePauseForLayer: theLayer];
CFTimeInterval mediaTime = CACurrentMediaTime();
CFTimeInterval timeSincePause = [theLayer convertTime: mediaTime fromLayer:nil] - pausedTime;
theLayer.beginTime = timeSincePause;
}
I also tried the remove animation property of the CAanimtion.It works!! but i cant able to resume the animation from the paused the position.
Please help me.
Thanks in Advance.