Tometoyou
Tometoyou

Reputation: 8376

CAShapeLayer stroke animated with CADisplayLink not completed

I have set up a CADisplayLink that calls the following drawCircle() function to draw a circle path animation in 10 seconds:

func drawCircle() {
    currentDuration = currentDuration + displayLink.duration
    circleLayer.strokeEnd = min(CGFloat(currentDuration/maxDuration), 1)

    if (currentDuration >= maxDuration) {
        stopCircleAnimation()
    }
}

func stopCircleAnimation() {
    let pausedTime = circleLayer.convertTime(CACurrentMediaTime(), fromLayer: nil)
    circleLayer.speed = 0
    circleLayer.timeOffset = pausedTime
}

where currentDuration is the elapsed time, and maxDuration is equal to 10. This works fine, except when currentDuration >= maxDuration. Even though the strokeEnd is set to 1, it never fully completes the circle. Why is this??

EDIT

I think it could have something to do with the speed property of the circleLayer. If I set it to a higher amount, e.g. 10, then the circle is completely closed.

Upvotes: 1

Views: 360

Answers (2)

Hamish
Hamish

Reputation: 80781

This is due to the fact that setting the strokeEnd of your CAShapeLayer generates an implicit animation to the new value. You then set the layer's speed to zero before this animation is complete, therefore 'pausing' the animation, so that it appears 'incomplete'.

While you can work around by disabling implicit animations through setDisableActions – you should probably be considering if using a CADisplayLink is really appropriate here. Core Animation is designed to generate and run animations for you in the first place by generating its own intermediate steps, so why not achieve the same result with an explicit or implicit animation of your layer's strokeEnd?

Here's an example of how you could do this with an implicit animation:

CATransaction.begin()
CATransaction.setAnimationDuration(10)

// if you really want a linear timing function – generally makes the animation look ugly
CATransaction.setAnimationTimingFunction(CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear))

circleLayer.strokeEnd = 1
CATransaction.commit()

Or if you want it as an explicit animation:

let anim = CABasicAnimation(keyPath: "strokeEnd")
anim.fromValue = 0
anim.toValue = 1
anim.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
anim.duration = 10
circleLayer.addAnimation(anim, forKey: "strokeEndAnim")

// update model layer value
CATransaction.begin()
CATransaction.setDisableActions(true)
circleLayer.strokeEnd = 1
CATransaction.commit()

Upvotes: 1

Tometoyou
Tometoyou

Reputation: 8376

Found the answer – disable animations when setting the strokeEnd property:

CATransaction.begin()
CATransaction.setDisableActions(true)
circleLayer.strokeEnd = min(CGFloat(currentDuration/maxDuration), 1)
CATransaction.commit()

Upvotes: 0

Related Questions