How to animate only a percentage of the full CALayer animation along a UIBezierPath?
Asked Answered
M

4

8

I manage to animate an CALayer along a UIBezierPath.

What i'm trying to accomplish is to animate only a percentage of the path, for example, only 25% of the path, with the layer staying at that position (at 25%).

What is the way to do this? Here is my code, it always animate the full path.

let aPath = UIBizierPath(CGPath: somePath)
let anim = CAKeyframeAnimation(keyPath: "position")
anim.path = aPath.CGPath
anim.rotationMode = kCAAnimationRotateAuto
anim.repeatCount = 1
anim.fillMode = kCAFillModeForwards
anim.removedOnCompletion = false
anim.duration = 3.0
anim.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)

ticker.addAnimation(anim, forKey: "animate_ticker")
Microhenry answered 5/10, 2015 at 2:4 Comment(0)
S
3

Simple change one property:

anim.repeatCount = 0.25    

Here is an excellent article on animation timing,you can learn how to have even more fine-grained control over your animations.

Append:
1. To achieve what you want ,the closest way is to have an 25% subPath ,here is some helper method.

2.If you can stand the speed difference,Use the method above, and set back the position when animation ends:

ticker.position = ticker.presentationLayer().position
Strawboard answered 15/5, 2016 at 9:32 Comment(2)
The problem with this is that the animation won't stop 1/4 of the way through the path, only 1/4 of the way through the animation. Therefore with a kCAMediaTimingFunctionEaseInEaseOut timing function and 0.25 for the repeatCount, the animation will stop sooner than expected. Furthermore, the timing function will still be applied to the entire animation's duration – and therefore it'll appear to speed up and then suddenly stop, which may or may not be what OP wants.Pedicure
@originaluser2, you are correct. I found the animation stopping not perfectly where I thought it would, so this explains it! Thanks! I am looking for 1/4 of the path, not the animation.Microhenry
H
1

Not only CGPath is opaque, but also CAAnimation doesn't support any updates or notifications for ongoing animations (i.e. after started but before finished). The only entities involved are animation itself and CALayer-s it is applied to.

So, options are:

(some options may sound too scary but they are not, so I made an example project, see below)

repeatCount

As wj2061 mentioned is his answer you can tweak animation's repeatCount. Cons is it will animate 0.25 of animation time, not 0.25 of path

CAShapeLayer

If, by any chance, you can represent your ticker with CAShapeLayer segment then you can animate strokeStart and strokeEnd so it will look like segment is moving along the path

Clever repeatCount

You can calculate such repeatCount value that animation will stop at 0.25 of path.

  • grab some bezier lib
  • extract bezier from timing function with getControlPointAtIndex:
  • solve bezier to get its "progress value" (usually named t) for which bezier's 'y' value will match to your "required progress" (0.25)
  • calculate bezier's 'x' value for that t - this is exact value of repeatCount you need

Animating with CAAnimation, all by yourself

kind of advanced

  • grab some bezier lib
  • create custom CALayer subclass with dynamic property, let's say rideProgress
  • add synthesized properties for bezier path (from bezier lib, not CGPath) and sublayer (say, rideLayer) to animate
  • override needsDisplayForKey: for rideProgress key and initWithLayer: for all introduced properties BUT use self.rideLayer?.presentationLayer() instead of copying rideLayer
  • override one of these: drawInContext: and draw what you need in it; or (better) display, in display retrieve current rideProgress value from presentationLayer and update sublayer, then call super. Either way, use bezier lib to calculate position and rotation for sublayer accordingly to current rideProgress value
  • don't forget to CATransaction.setDisableActions(true) before setting any animatable property of any layer
  • finally, use any kind of CAAnimation or implicit animation on rideProgress property

Split path

Again, suggested by wj2061. You can split the path so one of halves will represent 0.25 of original


Here is example implementation of everything above EXCEPT "Split path". I didn't run any field tests on this code, it's just a working concept. XCode 7.3.1, Swift.


Materials used: Wiki on Cubic function, Great article about operations on beziers, Cubic equations solving method line-by-line

Hollah answered 20/5, 2016 at 3:33 Comment(0)
M
0

Did you try to set fromValue and toValue properties? It should work if you set fromValue to 0.0 and toValue to 0.25

Mckinley answered 15/5, 2016 at 10:9 Comment(1)
fromValue and toValue are introduced in CABasicAnimation which is not a superclass to CAKeyframeAnimationHollah
C
0

I was running into the same issue, and I wanted to animate the drawing of a Path but only a percentage of it.

What I ended up doing was setting the layer's strokeStart and strokeEnd to same floating point values as the animation.

So in your example, try setting anim.fromValue = 0.0f, anim.toValue = 0.6f and also ticker.strokeStart=0.0f and ticker.strokeEnd = 0.6f.

This successfully drew only 60% of the path and animated it too!

Confederate answered 9/6, 2016 at 6:30 Comment(1)
I don't remember the exact reasons, but this didn't work. (I'm not the one who downvoted)Microhenry

© 2022 - 2024 — McMap. All rights reserved.