Oleg Gordiichuk
Oleg Gordiichuk

Reputation: 15512

Swift 3 animation issue

I am implementing progress view that simply animate colors.

Everything is working fine exept "strokeEnd" animation that is filling by shape and making it to flicker.

Video resource

Code example:

class MaterialCircularProgressView: UIView {

    let circularLayer = CAShapeLayer()
    let googleColors = [
        UIColor(red:0.282, green:0.522, blue:0.929, alpha:1),
        UIColor(red:0.859, green:0.196, blue:0.212, alpha:1),
        UIColor(red:0.957, green:0.761, blue:0.051, alpha:1),
        UIColor(red:0.235, green:0.729, blue:0.329, alpha:1)
    ]

    let inAnimation: CAAnimation = {
        let animation = CABasicAnimation(keyPath: "strokeEnd")
        animation.fromValue = 0.0
        animation.toValue = 1.0
        animation.duration = 1.0
        animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)

        return animation
    }()

    let outAnimation: CAAnimation = {
        let animation = CABasicAnimation(keyPath: "strokeStart")
        animation.beginTime = 0.5
        animation.fromValue = 0.0
        animation.toValue = 1.0
        animation.duration = 1.0
        animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)

        return animation
    }()

    let rotationAnimation: CAAnimation = {
        let animation = CABasicAnimation(keyPath: "transform.rotation.z")
        animation.fromValue = 0.0
        animation.toValue = 2 * M_PI
        animation.duration = 2.0
        animation.repeatCount = MAXFLOAT

        return animation
    }()

    var colorIndex : Int = 0

    override init(frame: CGRect) {
        super.init(frame: frame)

        circularLayer.lineWidth = 4.0
        circularLayer.fillColor = nil
        layer.addSublayer(circularLayer)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func layoutSubviews() {
        super.layoutSubviews()

        let center = CGPoint(x: bounds.midX, y: bounds.midY)
        let radius = min(bounds.width, bounds.height) / 2 - circularLayer.lineWidth / 2

        let arcPath = UIBezierPath(arcCenter: CGPoint.zero, radius: radius, startAngle: CGFloat(M_PI_2), endAngle: CGFloat(M_PI_2 + (2 * M_PI)), clockwise: true)

        circularLayer.position = center
        circularLayer.path = arcPath.cgPath

        animateProgressView()
        circularLayer.add(rotationAnimation, forKey: "rotateAnimation")
    }


    func animateProgressView() {

        circularLayer.removeAnimation(forKey: "strokeAnimation")

        circularLayer.strokeColor = googleColors[colorIndex].cgColor

        let strokeAnimationGroup = CAAnimationGroup()
        strokeAnimationGroup.duration = 1.0 + outAnimation.beginTime
        strokeAnimationGroup.repeatCount = 1
        strokeAnimationGroup.animations = [inAnimation, outAnimation]
        strokeAnimationGroup.delegate = self

        circularLayer.add(strokeAnimationGroup, forKey: "strokeAnimation")

        colorIndex += 1;
        colorIndex = colorIndex % googleColors.count;
    }
}


extension MaterialCircularProgressView: CAAnimationDelegate{

    override func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
        if(flag) {
            animateProgressView()
        }
    }

}

Upvotes: 1

Views: 684

Answers (1)

alexburtnik
alexburtnik

Reputation: 7741

You can fix that flickering by setting strokeStart to the final value of your outAnimation in the initializer:

circularLayer.strokeStart = 1.0

The reason is that actual value of property animated by CAPropertyAnimation is not automatically set to the toValue of animation, so it reverts to it's original value which is 0.0 in your case. That's why a full circle appears for some time.

However there is a delay before next circle appears, not sure if that was your intention or another bug to be fixed.

Upvotes: 1

Related Questions