Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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")
like image 947
Van Du Tran Avatar asked Oct 05 '15 02:10

Van Du Tran


2 Answers

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
like image 83
wj2061 Avatar answered Nov 14 '22 12:11

wj2061


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

like image 1
gp-v Avatar answered Nov 14 '22 12:11

gp-v