Banana Banana - 11 months ago 234
Swift Question

CAAnimation to animate a checkmark drawing

I am trying to make a simple check mark control, which consists of just a square and a checkmark in it. I would like to animate drawing and erasing of the check mark when the control is tapped. This is what I have so far:

let path = UIBezierPath()
path.move(to: CGPoint(x: 0, y: checkBox.size * 2 / 3))
path.addLine(to: CGPoint(x: checkBox.size / 3, y: checkBox.size))
path.addLine(to: CGPoint(x: checkBox.size, y: 0))

pathLayer.frame = checkBox.bounds
pathLayer.path = path.cgPath
pathLayer.strokeColor = UIColor.red().cgColor
pathLayer.fillColor = nil
pathLayer.lineWidth = checkMarkWidth
pathLayer.lineJoin = kCALineJoinBevel

let pathAnimation = CABasicAnimation(keyPath:"strokeEnd")
pathAnimation.duration = checkMarkAnimationDuration
pathAnimation.fromValue = NSNumber(floatLiteral: 0)
pathAnimation.toValue = NSNumber(floatLiteral: 1)

let reverseAnimation = CABasicAnimation(keyPath:"strokeEnd")
reverseAnimation.duration = checkMarkAnimationDuration
reverseAnimation.fromValue = NSNumber(floatLiteral: 1)
reverseAnimation.toValue = NSNumber(floatLiteral: 0)
reverseAnimation.delegate = self


Then I start the animations like this:

var on: Bool = false {
didSet {
if on {
checkBox.layer.addSublayer(pathLayer)
pathLayer.removeAllAnimations()
pathLayer.add(pathAnimation, forKey:"strokeEnd")
} else {
pathLayer.removeAllAnimations()
pathLayer.add(reverseAnimation, forKey:"strokeEnd")
}
}
}


Finally in my CAAnimationDelegate I do this:

public func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
pathLayer.removeFromSuperlayer()
}


This looks good, except that when I test it on the real device there is a little flash when the reversed animation is done, but before the layer is removed. It seems I am missing an
animationWillStop
callback.

enter image description here

Any ideas how to fix this glitch?

Also any other suggestions on how to improve this code are welcome, cause I'm completely new to Core Animation.

Answer Source

Core Animation has three layer tree, one is model layer tree, which you mostly interact with. one is presentation layer tree, which contains in-flight running animation values. and the last one is render layer tree, which actually render layer and private to Core Animation.

CAAnimation Only change value on CALayer's presentationLayer. after animation end, the animation is removed and strokeEnd jump back to your modal layer's value: 1.0. This is why you have flash.

so, you can change

var on: Bool = false {
    didSet {
        if on {
            checkBox.layer.addSublayer(pathLayer)
            pathLayer.removeAllAnimations()
            pathLayer.add(pathAnimation, forKey:"strokeEnd")
        } else {
            pathLayer.removeAllAnimations()
            pathLayer.add(reverseAnimation, forKey:"strokeEnd")
        }
    }
}

to

var on: Bool = false {
    didSet {
        if on {
            checkBox.layer.addSublayer(pathLayer)
            pathLayer.strokeEnd = 1.0 // set property to final state
            pathLayer.removeAllAnimations()
            pathLayer.add(pathAnimation, forKey:"strokeEnd")
        } else {
            pathLayer.strokeEnd = 0.0 // set property to final state
            pathLayer.removeAllAnimations()
            pathLayer.add(reverseAnimation, forKey:"strokeEnd")
        }
    }
}
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download