CAAnimation with timeOffSet - ios

I have an animation with 5.0 second duration. While an animation is being played, i pause it at 2.0 second and do some other things (moving to other viewcontroller, push, pop...). And then i back to this viewcontroller and continue animation from second 2.0. I made it work smooth with timeOffSet = 2.0, but still have an issue : It has 2 seconds surplus, that is the first 2 second of an animation.....How can i remove it ?
Animation with timeOffSet process:

Duplicated answer
-(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;
}

Related

How do I restart a CALayer animation going on?

Apple mentions these two methods for pausing and resuming a CALayer animation going on
-(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;
}
but how do I restart the animation?
this other SO question has nothing to do with what I need.
I have adopted code from blog in ViewController. Take a look at method [resetEmitterLayer:]. In your case all you need to do to reset animation is as follow:
Remove emitter layer from super layer.
Recreate emitter layer.
Add new emitter layer to the layer tree.
See ViewController implementation below
#import "EmitterViewController.h"
#import <QuartzCore/QuartzCore.h>
#interface EmitterViewController ()
#property (nonatomic, strong) CAEmitterLayer *emitterLayer;
#end
#implementation EmitterViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.emitterLayer = [self createEmitterLayer];
[self.view.layer addSublayer:self.emitterLayer];
}
-(CAEmitterLayer *)createEmitterLayer {
CAEmitterLayer *emitterLayer = [CAEmitterLayer layer];
emitterLayer.emitterPosition = CGPointMake(self.view.bounds.size.width / 2, self.view.bounds.origin.y);
emitterLayer.emitterZPosition = 10;
emitterLayer.emitterSize = CGSizeMake(self.view.bounds.size.width, 0);
emitterLayer.emitterShape = kCAEmitterLayerSphere;
CAEmitterCell *emitterCell = [CAEmitterCell emitterCell];
emitterCell.scale = 0.1;
emitterCell.scaleRange = 0.2;
emitterCell.emissionRange = (CGFloat)M_PI_2;
emitterCell.lifetime = 5.0;
emitterCell.birthRate = 10;
emitterCell.velocity = 200;
emitterCell.velocityRange = 50;
emitterCell.yAcceleration = 250;
emitterCell.contents = (id)[[UIImage imageNamed:#"WaterDrop.png"] CGImage];
emitterLayer.emitterCells = [NSArray arrayWithObject:emitterCell];
return emitterLayer;
}
-(IBAction)pauseEmitterLayer:(id)sender
{
CFTimeInterval pausedTime = [self.emitterLayer convertTime:CACurrentMediaTime() fromLayer:nil];
self.emitterLayer.speed = 0.0;
self.emitterLayer.timeOffset = pausedTime;
}
-(IBAction)resumeEmitterLayer:(id)sender
{
CFTimeInterval pausedTime = [self.emitterLayer timeOffset];
self.emitterLayer.speed = 1.0;
self.emitterLayer.timeOffset = 0.0;
self.emitterLayer.beginTime = 0.0;
CFTimeInterval timeSincePause = [self.emitterLayer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
self.emitterLayer.beginTime = timeSincePause;
}
-(IBAction)resetEmitterLayer:(id)sender
{
[self.emitterLayer removeFromSuperlayer];
self.emitterLayer = [self createEmitterLayer];
[self.view.layer addSublayer:self.emitterLayer];
}
#end

Stop animateWithDuration

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.

ios - cabasicanimation need to reset

I have 3 CABasicAnimation of 3 color bars which is following each other. After the third is completed, 3 bars will stay at their final position. Until here, everything is good, here is the code:
- (void)animationDidStop:(CABasicAnimation *)theAnimation finished:(BOOL)flag {
NSString* value = [theAnimation valueForKey:#"id"];
if([value isEqualToString:#"position1"]){
[self playVideoAtIndex:1];
}
else if([value isEqualToString:#"position2"]){
[self playVideoAtIndex:2];
}
else if([value isEqualToString:#"position3"]){
}
}
Before that, I created 3 animations like this:
-(void)createAnimationAtIndex:(NSInteger)index{
UILabel *label = (UILabel *)[barLabelArray objectAtIndex:index];
if(index==0){
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"position"];
animation.delegate = self;
//SOMETHING HERE
[label.layer addAnimation:animation forKey:#"position1"];
}
else if(index==1){
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"position"];
animation.delegate = self;
//SOMETHING HERE
[self.delegate startPlayVideoAtIndex:1];
[label.layer addAnimation:animation forKey:#"position2"];
}
else if(index==2){
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"position"];
animation.delegate = self;
//SOMETHING HERE
[label.layer addAnimation:animation forKey:#"position3"];
}
}
So if I wait until all animations stop, when I come back, they will start animation properly again. But sometimes I need stoping the animation in the middle of it. And then when I come back and start the animation again, all is messed up. Here is code for stoping animations:
-(void)reset{
for(UILabel *label in barLabelArray){
[label.layer removeAllAnimations];
}
}
So do you know what I am doing wrong here and how to fix it? Thank you !!
There is a way pause and resume any animations. Here is several good strings which can help you if I understand correctly
- (void) pauseLayer
{
CFTimeInterval pausedTime = [label.layer convertTime:CACurrentMediaTime() fromLayer:nil];
label.layer.speed = 0.0;
label.layer.timeOffset = pausedTime;
}
- (void) resumeLayer
{
CFTimeInterval pausedTime = [label.layer timeOffset];
label.layer.speed = 1.0;
label.layer.timeOffset = 0.0;
label.layer.beginTime = 0.0;
CFTimeInterval timeSincePause = [label.layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
label.layer.beginTime = timeSincePause;
}

Comprehend pause and resume animation on a layer

I am studying Animation in Core Animation Programming Guide and I get stuck on comprehending pause and resume animation on a layer.
The document tells me how to pause and resume animation without clear explanation. I think the key is to understand what is timeOffset and beginTime method of CAlayer.
These code is pause and resume animation. In resumeLayer method, layer.beginTime = timeSincePause; this line really make me confused.
-(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;
}
Any help will be appreciated.
Let's have a test at the two properties of a layer: beginTime and timeOffset.
Prerequisite
We get the CALayer's time space using [layer convertTime:CACurrentMediaTime() fromLayer:nil]
1、Assign 5.0 to layer's beginTime (beginTime is 0 before) :
NSLog(#"CACurrentMediaTime:%f", [t1.layer convertTime:CACurrentMediaTime() fromLayer:nil]) ;
t1.layer.beginTime = 5.0 ;
NSLog(#"CACurrentMediaTime:%f",[t1.layer convertTime:CACurrentMediaTime() fromLayer:nil]) ;
result log is:
2014-01-15 11:00:33.811 newUserInterface[1404:70b] CACurrentMediaTime:7206.884498
2014-01-15 11:00:33.811 newUserInterface[1404:70b] CACurrentMediaTime:7201.885088
The result shows that if I add 5.0 on beginTime, the layer's time will minus 5.0. If an animation is in flight, adding 5.0 on beginTime will cause the animation redo the animation 5.0 seconds ago.
2、Assign 5.0 to layer's timeOffset (timeOffset is 0 before):
NSLog(#"CACurrentMediaTime:%f", [t1.layer convertTime:CACurrentMediaTime() fromLayer:nil]) ;
t1.layer.timeOffset = 5.0 ;
NSLog(#"CACurrentMediaTime:%f",[t1.layer convertTime:CACurrentMediaTime() fromLayer:nil]) ;
result is:
2014-01-15 11:09:07.757 newUserInterface[1449:70b] CACurrentMediaTime:7720.851464
2014-01-15 11:09:07.758 newUserInterface[1449:70b] CACurrentMediaTime:7725.852011
The result shows that if I add 5.0 on timeOffset, the layer's time will add 5.0. If an animation is in flight, adding 5.0 on timeOffset will cause the animation jump to the animation it would do 5.0 seconds later.
Comprehend pause and resume animation on a layer
Here is an example, t1 is an subview of UIViewController's root view. I do an animation on t1 which animates the position of t1.layer.
If an animation is added to a layer, the layer will calculate when to animate the animation according to the animation's beginTime, if the beginTime is 0, it will animate it immediately.
CABasicAnimation * b1 = [CABasicAnimation animationWithKeyPath:#"position"] ;
b1.toValue = [NSValue valueWithCGPoint:CGPointMake(160.0, 320.0)] ;
b1.duration = 10.0f ;
[t1.layer addAnimation:b1 forKey:#"pos"] ;
NSLog(#"CACurrentMediaTime:%f", [t1.layer convertTime:CACurrentMediaTime() fromLayer:nil]) ;
log shows 2014-01-15 11:25:53.975 newUserInterface[1530:70b] CACurrentMediaTime:8727.108740, which means the animation will begin at 8727 and stop at 8727+10 in t1.layer's time space.
When the animation is in flight, and I pause the animation using - (void)pauseLayer:(CALayer*)layer method.
layer.speed = 0.0; will cause the layer stop and layer's time will be set to 0. (I know that because when set layer.speed to 0, I immediately fetch the layer's time and log it)
layer.timeOffset = pausedTime; will add pauseTime to layer's time(assuming layer.timeOffset is 0), now the layer's time is pausedTime.
- (void)pauseLayer:(CALayer *)layer
{
CFTimeInterval pausedTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil]; // pauseTime is the time with respect to layer's time space
layer.speed = 0.0; // layer's local time is 0
layer.timeOffset = pausedTime; // layer's local time is pausedTime, so animation stop here
}
Then I will resume the animation using - (void)resumeLayer:(CALayer*)layer method.
-(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;
}
If I stop the animation at 8727+1(means the animation animate for 1 second), in pauseLayer method, layer.speed = 0 will set layer's time to 0 and layer.timeOffset = pausedTime; will add pausedTime on layer's time, so the layer's time is pausedTime.
Wait for a moment, let's have a summary now. layer.speed is 0.0, 'layer.timeOffset' is equal to pausedTime which is 8727+1 and layer's time is pausedTime too. Please keep in your mind, we will use them soon.
Let's continue, I resume the animation at 8727+11 with resumeLayer method, layer.speed = 1.0; it will add 8727+11 on layer's time, so layer's time is 8727+1+8727+11, layer.timeOffset = 0.0; it causes layer's time minus 8727+1 because layer.timeOffset is 8727+1 before, layer's local time is 8727+11 now. timeSincePause is (8727+11-8727-1)=10.
layer.beginTime = timeSincePause; it cause layer's time minus 10. Now layer's local time is 8727+1 which is the time I paused animation.
I will show you the code and log:
- (void)pauseLayer:(CALayer *)layer
{
NSLog(#"%f", CACurrentMediaTime()) ;
NSLog(#"pauseLayer begin:%f", [t1.layer convertTime:CACurrentMediaTime() fromLayer:nil]) ;
CFTimeInterval pausedTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil] ;
layer.speed = 0.0 ;
NSLog(#"pauseLayer after set speed to 0:%f",[t1.layer convertTime:CACurrentMediaTime() fromLayer:nil]) ;
layer.timeOffset = pausedTime ;
NSLog(#"pauseLayer after set timeOffset:%f",[t1.layer convertTime:CACurrentMediaTime() fromLayer:nil]) ;
}
- (void)resumeLayer:(CALayer *)layer
{
NSLog(#"%f", CACurrentMediaTime()) ;
NSLog(#"resumeLayer begin:%f",[t1.layer convertTime:CACurrentMediaTime() fromLayer:nil]) ;
CFTimeInterval pausedTime = layer.timeOffset ;
layer.speed = 1.0 ;
NSLog(#"resumeLayer after set speed to 1:%f",[t1.layer convertTime:CACurrentMediaTime() fromLayer:nil]) ;
layer.timeOffset = 0.0;
NSLog(#"resumeLayer after set timeOffset to 0:%f",[t1.layer convertTime:CACurrentMediaTime() fromLayer:nil]) ;
layer.beginTime = 0.0 ;
NSLog(#"resumeLayer after set beginTime to 0:%f",[t1.layer convertTime:CACurrentMediaTime() fromLayer:nil]) ;
CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime ;
layer.beginTime = timeSincePause ;
NSLog(#"resumeLayer after set beginTime to timeSincePause:%f",[t1.layer convertTime:CACurrentMediaTime() fromLayer:nil]) ;
}
log:
2014-01-15 13:14:34.157 newUserInterface[1762:70b] 15247.550325
2014-01-15 13:14:34.158 newUserInterface[1762:70b] pauseLayer begin:15247.550826
2014-01-15 13:14:34.158 newUserInterface[1762:70b] pauseLayer after set speed to 0:0.000000
2014-01-15 13:14:34.159 newUserInterface[1762:70b] pauseLayer after set timeOffset:15247.551284
2014-01-15 13:14:40.557 newUserInterface[1762:70b] 15253.950505
2014-01-15 13:14:40.558 newUserInterface[1762:70b] resumeLayer begin:15247.551284
2014-01-15 13:14:40.558 newUserInterface[1762:70b] resumeLayer after set speed to 1:30501.502810
2014-01-15 13:14:40.559 newUserInterface[1762:70b] resumeLayer after set timeOffset to 0:15253.952031
2014-01-15 13:14:40.559 newUserInterface[1762:70b] resumeLayer after set beginTime to 0:15253.952523
2014-01-15 13:14:40.560 newUserInterface[1762:70b] resumeLayer after set beginTime to timeSincePause:15247.551294
Anther question is in resumeLayer method: why not combine the two assignment line to one layer.beginTime = timeSincePause;, the reason is in [layer convertTime:CACurrentMediaTime() fromLayer:nil], the result value is related to layer.beginTime.
layer.beginTime = 0.0;
CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
layer.beginTime = timeSincePause;
Tell the truth, I still don't know how animation works behind, what I am doing is analyzing the result, it's not a good solution. I'm very glad that anyone who has ideas on this could share. Thanks!
In the header file of CAMediaTiming, we can see these codes:
/* The begin time of the object, in relation to its parent object, if
* applicable. Defaults to 0. */
#property CFTimeInterval beginTime;
/* The basic duration of the object. Defaults to 0. */
#property CFTimeInterval duration;
/* The rate of the layer. Used to scale parent time to local time, e.g.
* if rate is 2, local time progresses twice as fast as parent time.
* Defaults to 1. */
#property float speed;
/* Additional offset in active local time. i.e. to convert from parent
* time tp to active local time t: t = (tp - begin) * speed + offset.
* One use of this is to "pause" a layer by setting `speed' to zero and
* `offset' to a suitable value. Defaults to 0. */
#property CFTimeInterval timeOffset;
What important is the formula:
t = (tp - begin) * speed + offset
This formula defines how the global time (or parent time, tp) maps to the local time of the layer. And this formula can explain everything of the listed codes:
At time A, the animation is paused. After sets the speed = 0, and timeOffset = pauseTime, the local time of the layer is equal pauseTime. And local time will not increase any more because speed = 0;
At time B, the animation is resumed. After sets the speed = 1.0, timeOffset = 0, beginTime = 0, the layer's local time is equal to the global time (or tp) which is (timePause + timeSinacePause). But we need the animation starts from the time point #A, so we set the beginTime = timeSincePaused, and then the layer's local time is equal to timePause. Of cause, this incurs the animation continue from the point paused.
From the docs:
beginTime
beginTime Specifies the begin time of the receiver in relation to its
parent object, if applicable. (required)
Here's a great explanation of it taken from here:
If an animation is in an animation group, beginTime is the offset from
the beginning of its parent object — the animation group. So if
beginTime of the animation is 5, it begins 5 seconds after the
animation group begins.
If an animation is added directly to a layer, beginTime is still the
offset from the beginning of its parent object — the layer. But since
the beginning of a layer is in the past1, I can not simply set
beginTime to 5 to delay the animation 5 seconds, because 5 seconds
after the beginning of a layer is probably still a past time. What I
usually really want is a delay relative to when the animation is added
to the layer — denoted by addTime.
This is the code from the question with an addition of logs:
- (IBAction)start:(UIButton *)sender
{
[UIView animateWithDuration:10 animations:^() {//Move square to x=300
}completion:^(BOOL finished){}];
}
- (IBAction)pause:(UIButton *)sender
{
CFTimeInterval pausedTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil];
layer.speed = 0.0;
layer.timeOffset = pausedTime;
NSLog(#"pausedTime: %f",pausedTime);
}
- (IBAction)resume:(UIButton *)sender
{
CFTimeInterval pausedTime = [layer timeOffset];
layer.speed = 1.0;
layer.timeOffset = 0.0;
layer.beginTime = 0.0;
NSLog(#"CACurrentMediaTime: %f",[layer convertTime:CACurrentMediaTime() fromLayer:nil]);
CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
NSLog(#"timeSincePause: %f",timeSincePause);
layer.beginTime = timeSincePause;
}
Output:
pausedTime: 20000
CACurrentMediaTime: 20005
timeSincePause: 5 // <- that's your begin time. When you hit resume you want to begin the animation from that relative time.
To sum it all up,
The animation duration is a total of 10, I stopped the animation at 5 and I also want it to be my beginTime when I resume the animation.
So, I save the pause time and subtract it from my current time in order to get the relative animation time that has passed.
I think the resume function only works for speed = 1, if speed is not equal 1 then the beginTime must be divided with speed to be corrected because beginTime is of type parent time, not line timeOffset that is local time.

Pause and Play CAKeyFrameAnimation in iOS

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.

Resources