Is there a way to achieve what the timeOffset property of an animation does, starting the animation at some point in time, but without the "wrapping" behaviour?
I have two animations (in two different layers):
// This is the animation of the stroke of a circle
let progressAnim = CABasicAnimation(keyPath: "strokeEnd")
progressAnim.duration = duration
progressAnim.fromValue = 0
progressAnim.toValue = 1
progressAnim.fillMode = kCAFillModeBackwards
progressAnim.timeOffset = elapsed
// This is the animation of a pointer that follow the same circle as above
let arrowAnim = CAKeyframeAnimation(keyPath: "position")
arrowAnim.duration = duration
arrowAnim.rotationMode = kCAAnimationRotateAuto
arrowAnim.path = arrowPath.CGPath
arrowAnim.fillMode = kCAFillModeBackwards
arrowAnim.timeOffset = elapsed
This starts the animations at the wanted progress, but when it reaches what is supposed to the the end, it starts over and lasts for the remaining duration. I realize this is how timeOffset is specified to work, but I was hoping that it is possible to somehow achieve the same without the wrap.
Instead of the timeOffset hack, you can calculate the proper fromValue and corresponding duration.
e.g.
progressAnim.duration = duration - elapsed
progressAnim.fromValue = elapsed / duration
Yes, it's possible. You'll need to use the same settings that you use to pause and resume an "in-flight" animation. The documentation is pretty weak, and the properties of the animation are confusing as hell. Every time I have to work with pausing and resuming animation I have to spend like half a day figuring it out again, and then a week later, I've forgotten how to do it again. It's been at least 6 months since I've dealt with it, so I really forget how to do it.
I have a demo project on github that shows how to pause and resume an animation, and even to use a slider to move an animation forward and backward. I suggest you take a look at that. It has all the pieces you need:
Keyframe view animation demo on Github
I think I figured it out. By trial and error, it seems to work when I set beginTime of the animation to a time in the past, like this:
let beginTime = CACurrentMediaTime() - offset
// Set the beginTime of the progress animation
progressAnim.beginTime = beginTime
// Set the begintTime of the arrow animation
arrowAnim.beginTime = beginTime
This seems to have the desired effect.
Related
Sorry for my poor english.
In my work, We made a app which show a high resolution image into a UIScrollVIew. Besides, the user can play a MP3 that animate the position and zoom of UIScrollView from one second with a specific duration showing the detail of image that MP3 is talking about. Currently, the audio play from second 0 to the end sequently. User can´t interactuate with UIScrollView when the MP3 is playing
Now we want that the user can change the current time when is playing, I´m not sure how calculate the position when the user select a second placed in the midst of one animation.
Example:
Second 1: Duration 4; Second 8: Duration 2; Second 20 Duration 10; Second 31 Duration 3;
If the user select the second 24, we would want animate during 6 seconds, but keeping in mind the original position that UIScrollView should have since second 20 to 24 from original animation.
We are using uiview animation (animateWithDuration) with zoomToRect (animated:NO) method from UIScrollView
I´m not sure how calculate those intermediate values.
Thanks.
I do not know, whether I really get you right or wrong. But you can set the animations progress to a progress value that is in relation to the start point and the total duration of the animation. I. e. in your case
float animationStartPoint = 20.0; // Start of the animation
float duration = 10.0; // Its duration
float partialStartPoint = 24.0; // user selected start point
NSAnimation *animation = …;
…
animation.currentProgress = (partialStartPoint-animationStartPoint)/duration;
[animation startAnimation];
I resume my CABasicAnimation with the following code:
CFTimeInterval pausedtime = layer.timeOffset;
layer.speed = 1.0;
layer.timeOffset = 0.0;
layer.beginTime = [layer convertTime:CACurrentMediaTime() toLayer:nil] - pausedTime;
which is pretty straightforward. At the very end of this article the author states that with negative speed value animation reverses. I can't understand what timeOffset and beginTime should look like in this case?
P.S. I am aware that I can reverse animation by obtaining current values from presentation layer and setting toValue and fromValue. I want to figure out if it is possible with speed property.
You will have problems with resuming an animation backwards with .timeOffset = 0. .timeOffset = 0 means that the animation is at its beginning. So, with reverse speed the animation will be finished immediately. Thus, if you, for example, have animation which is removed automatically on completion — it won't play anything: just will disappear.
CAAnimation has poor documentation indeed. And their example of resuming uses .beginTime property only. We can use .timeOffset property instead.
1. Set your layer's begin time to the current time of its container:
// set begin time to current time of superlayer to start immediately
let sTime = layer.superlayer?.convertTime(CACurrentMediaTime(), to: nil) ?? 0
layer.beginTime = sTime
2. Go to the moment of animation, where you want to resume, using .timeOffset:
// set starting point of animation to the current "progress"
layer.timeOffset = progress
Where progress is the time position you want to resume from in terms of animation duration. i.e. if you have an animation duration of 0.6 — then 0 ≤ progress ≤ 0.6.
3. Launch animation in reverse direction:
// set negative speed to have reverse animation
layer.speed = -1.0
Don't forget to remove animation on its completion, if needed.
This is the picture of how these settings are applied in fact:
I'm trying to make a small ui controlled animation (as described in this answer) and continue this animation after rotation. Due to autolayout issues I have to remove and add the whole layer on autorotate.
This works so far. My problem is, as I want continue the animation from the same position it was left off, it won't go anywhere prior to the state it was the moment it rotated.
e.g. the slider is at 0.5. The animation was added again (due to removal) and I've set the timeOffset corresponding to 0.5. The animation will go forward, but not backward.
I create my animation via:
let animatePhase = CABasicAnimation(keyPath: "lineDashPhase")
animatePhase.byValue = phaseLength
animatePhase.duration = 1.0
animatePhase.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
animatePhase.repeatCount = Float.infinity
lineLayer.addAnimation(animatePhase, forKey: "marching ants")
lineLayer.speed = 0.0
lineLayer.timeOffset = 0.0;
lineLayer.beginTime = 0.0;
lineLayer is a CAShapeLayer.
On `layoutSubviews' I remove and recreate the layer.
What am I doing wrong?
Thanks
I have an animation that animates a layer along a curved path and then back. I would like the layer to pause before the auto-reversing kicks in. Is there an easy way to do this with Core Animation?
Here is the code I'm using to start the animation.
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
animation.duration = animationDuration;
animation.path = curvedPath;
animation.rotationMode = kCAAnimationRotateAuto;
animation.delegate = nil;
animation.autoreverses = YES;
[self.myView.layer addAnimation:animation forKey:#"cardAnimation"];
The only way I can think of to pause in an auto-reversing animation is to start a timer for 1/2 the animation time.
When the timer fires, set the speed of your animation to 0, and launch another timer. When the second timer fires, set the speed of your animation back to 1 (or whatever it was before) again. That should look good if you're using ease-in, ease-out animation timing, since the animation slows to a stop before reversing.
Failing that, you'd probably have to create separate forward and reverse animations, set a delegate for the animation, and in the completion method, pause and then start the reverse animation.
I trying to find a way for connecting user interaction (pan gesture) with animation. I want to control animation progress by dragging view (or just by finger panning on the screen).
I think I can make this by using Core Animation, but I didn't find any similar to my needs example.
I have a CGPath, that I want to draw.
I know that there are strokeStart and strokeEnd property that is animatable and perfectly acceptable for my needs in the "drawing" point of view.
Using layer with given path property I can animate this path with CABasicAnimation
layer.path = myPath
//... layer configuration
let pathAnimation = CABasicAnimation(keyPath: "strokeEnd")
pathAnimation.duration = 2
pathAnimation.fromValue = 0
pathAnimation.toValue = 1
layer.addAnimation(pathAnimation, forKey: "strokeEndAnimation")
Here we can see animation from the first path point to the last consequentially.
Okay, but I need to somehow control this animation with gesture.
Let's say, if we pan finger on the screen to the right, our path animation draws straight ahead (e.g strokeStart = 0 and strokeEnd = 1) and if we pan from right to left we will see backward animation (like rewind) (e.g. strokeStart = 1 and strokeEnd = 0).
I know, how to get gesture direction, translationInView, velocityInView and I want to animate this in changed state, not end. (UIGestureRecognizerState.Changed)
But I don't know how I can combine control from gesture to animation because of this troubles:
duration of animation. (I don't know how fast or how slow gesture would be)
how I need to implement animation because calls are very frequent in changed state
I looked to
animateWithDuration:delay:usingSpringWithDamping:initialSpringVelocity:options:animations:completion:
but duration question rises again.
Hope somebody can help me, ask me if you misunderstood something and I'll try to clarify.
Let's say, if we pan finger on the screen to the right, our path animation draws straight ahead (e.g strokeStart = 0 and strokeEnd = 1) and if we pan from right to left we will see backward animation (like rewind) (e.g. strokeStart = 1 and strokeEnd = 0).
If I understand you correctly, the solution is to freeze the animation by setting the layer's speed to 0 and then setting the "frame" of the animation by setting the layer's timeOffset.