Pause and Play CAKeyFrameAnimation in iOS - 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.

Related

Draw a circle on a Long Press event

I am drawing a circle on the screen as the user taps on a button. The animation duration is already set and also the from and to value are also set.
What I want to achieve is that somehow the animation should commence as the user long presses the button and continues till he is maintaining the tap on the screen i.e for the duration of Long Press.
As soon as the user lifts his finger the circle should stop to the point to where it has been completed till now.
Here is my code:
-(void)startCircularAnimation{
int radius = 50;
CAShapeLayer *circle = [CAShapeLayer layer];
// Make a circular shape
circle.path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, 2.0*radius, 2.0*radius)
cornerRadius:radius].CGPath;
// Center the shape in self.view
circle.position = CGPointMake(CGRectGetMidX(self.view.frame)-radius,
CGRectGetMidY(self.view.frame)-radius);
// Configure the apperence of the circle
circle.fillColor = [UIColor clearColor].CGColor;
circle.strokeColor = [UIColor redColor].CGColor;
circle.lineWidth = 5;
// Add to parent layer
[self.view.layer addSublayer:circle];
// Configure animation
CABasicAnimation *drawAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
drawAnimation.duration = 15.0;
drawAnimation.repeatCount = 1.0; // Animate only once..
// Animate from no part of the stroke being drawn to the entire stroke being drawn
drawAnimation.fromValue = [NSNumber numberWithFloat:0.0f];
drawAnimation.toValue = [NSNumber numberWithFloat:counter/drawAnimation.duration];
// Experiment with timing to get the appearence to look the way you want
drawAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
// Add the animation to the circle
[circle addAnimation:drawAnimation forKey:#"draw"];
}
This method performs animation and the from value is calculated from a timer which I started on the touch began case of the long press handler method. I am not able to get the perfect duration for the long press.
The long press event methods is something like this.
- (void)_handleLongPressGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer{
switch (gestureRecognizer.state) {
case UIGestureRecognizerStateBegan:
{
counter = 0;
timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(incrementCounter) userInfo:nil repeats:YES];
}
case UIGestureRecognizerStateEnded:{
NSLog(#"State ended");
[timer invalidate];
break;
}
case UIGestureRecognizerStateCancelled:{
NSLog(#"State cancelled");
break;
}
case UIGestureRecognizerStateFailed:
{
break;
}
default:
break;
}
}
and the increment counter method is as follows
- (void)incrementCounter {
counter++;
[self startCircularAnimation];
}
This is not giving me the desired effect for drawing the circle till the user has his finger on the screen.
Please suggest something in the code to get the desired functionality.
Thanks in advance.
You'll want to follow apples guidelines https://developer.apple.com/library/ios/qa/qa1673/_index.html
So in your interface i'd declare the following
#interface ViewController ()
#property (nonatomic, strong) CAShapeLayer *circle;
#property (nonatomic, strong) CABasicAnimation *drawAnimation;
#property (strong, nonatomic) IBOutlet UIButton *circleButton;
#end
Then in view did load
- (void)viewDidLoad
{
[super viewDidLoad];
int radius = 50;
self.circle = [CAShapeLayer layer];
// Make a circular shape
self.circle.path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, 2.0*radius, 2.0*radius)
cornerRadius:radius].CGPath;
// Center the shape in self.view
self.circle.position = CGPointMake(CGRectGetMidX(self.view.frame)-radius,
CGRectGetMidY(self.view.frame)-radius);
// Configure the apperence of the circle
self.circle.fillColor = [UIColor clearColor].CGColor;
self.circle.strokeColor = [UIColor redColor].CGColor;
self.circle.lineWidth = 5;
self.circle.strokeEnd = 0.0f;
// Add to parent layer
[self.view.layer addSublayer:_circle];
// Target for touch down (hold down)
[self.circleButton addTarget:self action:#selector(startCircleAnimation) forControlEvents:UIControlEventTouchDown];
// Target for release
[self.circleButton addTarget:self action:#selector(endCircleAnimation) forControlEvents:UIControlEventTouchUpInside];
/**
Don't start Animation in viewDidLoad to achive the desired effect
*/
}
Function to start the animation and resume it (probably needs a better name)
-(void)startCircleAnimation{
if (_drawAnimation) {
[self resumeLayer:_circle];
} else {
[self circleAnimation];
}
}
Function to end animation
-(void)endCircleAnimation{
[self pauseLayer:_circle];
}
Function to generate animation
- (void)circleAnimation
{
// Configure animation
self.drawAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
self.drawAnimation.duration = 10.0;
self.drawAnimation.repeatCount = 1.0; // Animate only once..
// Animate from no part of the stroke being drawn to the entire stroke being drawn
self.drawAnimation.fromValue = [NSNumber numberWithFloat:0.0f];
// Set your to value to one to complete animation
self.drawAnimation.toValue = [NSNumber numberWithFloat:1.0f];
// Experiment with timing to get the appearence to look the way you want
self.drawAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
// Add the animation to the circle
[self.circle addAnimation:_drawAnimation forKey:#"draw"];
}
Pause and stop functions from apple
- (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;
}
The main point you'd want to take from this is that your not keeping account of how long the button is pressed for your just keeping account of the events that are sent from the button UIControlEventTouchDown and UIControlEventTouchUpInside
Edit:Gif
I think you should make your from value related to value of counter, this will make the drawing start from what it was left behind last time the user lift his finger.
You should also make your timer's time interval smaller, 1 second is too long, 0.1 second will be better.

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.

CAKeyframeAnimation Manual Progress

I have a UIView whose backing layer has a CAKeyframeAnimation with a simple straight line path set as its `path`.
Can I have the animation "frozen", so to speak, and manually change its progress?
For example:
If the path is 100 points in length, setting the progress (offset?) to 0.45 should have the view move 45 points down the path.
I remember seeing an article that did something similar (moving a view along a path based on the value from a slider) via CAMediaTiming interfaces, but I haven't been able to find it, even after a few hours of searching. If I'm approaching this in a completely wrong way, please do let me know. Thanks.
Here's some sample code, if the above isn't clear enough.
- (void)setupAnimation
{
CAKeyFrameAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:_label.layer.position];
[path addLineToPoint:(CGPoint){200, 200}];
animation.path = path.CGPath;
animation.duration = 1;
animation.autoreverses = NO;
animation.removedOnCompletion = NO;
animation.speed = 0;
// _label is just a UILabel in a storyboard
[_label.layer addAnimation:animation forKey:#"LabelPathAnimation"];
}
- (void)sliderDidSlide:(UISlider *)slider
{
// move _label along _animation.path for a distance that corresponds to slider.value
}
This is based on what Jonathan said, only a bit more to the point. The animation is set up correctly, but the slider action method should be as follows:
- (void)sliderDidSlide:(UISlider *)slider
{
// Create and configure a new CAKeyframeAnimation instance
CAKeyframeAnimation *animation = ...;
animation.duration = 1.0;
animation.speed = 0;
animation.removedOnCompletion = NO;
animation.timeOffset = slider.value;
// Replace the current animation with a new one having the desired timeOffset
[_label.layer addAnimation:animation forKey:#"LabelPathAnimation"];
}
This will make the label move along the animation's path based on timeOffset.
Yes you can do this with the CAMediaTiming interface. You can set the speed of the layer to 0 and manualy set the timeOffset. Example of a simple pause/resume method:
- (void)pauseAnimation {
CFTimeInterval pausedTime = [yourLayer convertTime:CACurrentMediaTime() fromLayer:nil];
yourLayer.speed = 0.0;
yourLayer.timeOffset = pausedTime;
}
- (void)resumeAnimation {
CFTimeInterval pausedTime = [yourLaye timeOffset];
if (pausedTime != 0) {
yourLayer.speed = 1.0;
yourLayer.timeOffset = 0.0;
yourLayer.beginTime = 0.0;
CFTimeInterval timeSincePause = [yourLayer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
yourLayer.beginTime = timeSincePause;
}
}

Pausing CALayer animation with an animation with a delay

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.

Resources