David Henry
David Henry

Reputation: 3054

CABasicAnimation Stroke End Animation Percentage

I have a custom progress view that animates based on Character Count. (similar to twitter)

However, my calculation seems to be off. I want the stroke layer to circle around based on the percentage.

Here's my code:

class CharacterCountView: UIView {

private let progressLayer = CAShapeLayer()

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

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

@objc func animateToValue(value: Double) {
    let animation = CABasicAnimation(keyPath: "strokeEnd")
    animation.toValue = value
    animation.duration = 0.5
    animation.fillMode = .forwards
    animation.isRemovedOnCompletion = false
    progressLayer.add(animation, forKey: "strokeAnimation")
    progressLayer.strokeEnd = value
    
    if value > 1 {
        print("100% REACHED")
    }
}
}

extension CharacterCountView {

fileprivate func setUpView() {
    addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(animateToValue)))
    let trackLayer = CAShapeLayer()
    trackLayer.frame = bounds
    let path = UIBezierPath(arcCenter: trackLayer.position, radius: 8, startAngle: -CGFloat.pi / 2, endAngle: 2 * CGFloat.pi, clockwise: true)
    trackLayer.path = path.cgPath
    trackLayer.strokeColor = UIColor.systemGray2.cgColor
    trackLayer.lineWidth = 1.5
    trackLayer.fillColor = Color.background.cgColor
    trackLayer.lineCap = .round
    layer.addSublayer(trackLayer)
    progressLayer.frame = bounds
    progressLayer.path = path.cgPath
    progressLayer.strokeColor = Color.accent.cgColor
    progressLayer.lineWidth = 1.5
    progressLayer.fillColor = UIColor.clear.cgColor
    progressLayer.lineCap = .round
    progressLayer.strokeEnd = 0
    layer.addSublayer(progressLayer)
}
}

In my Text Did Change Function:

func textDidChange(to value: String) {
        if value.isEmpty || value.count > 280 {
            postBarButtonItem.isEnabled = false
        } else {
            postBarButtonItem.isEnabled = true
        }
    
    let characterCount = Double(value.count)
    let percentage = characterCount / 280
    print(percentage)
    createPostInputAccessoryView.characterCountView.animateToValue(value: percentage)
}

Its topping out at around 70% what is wrong with my calculation?

Upvotes: 0

Views: 104

Answers (1)

DonMag
DonMag

Reputation: 77462

Your endAngle is wrong...

Let's make it a little more "readable":

let path = UIBezierPath(arcCenter: trackLayer.position,
                        radius: 8,
                        startAngle: -CGFloat.pi / 2,
                        endAngle: 2 * CGFloat.pi,
                        clockwise: true)

This: startAngle: -CGFloat.pi / 2 is minus 90-degrees, or 12 o'clock on the circle.

But this: endAngle: 2 * CGFloat.pi is 360-degrees, or 3 o'clock

So you have 450-degrees instead of 360-degrees.

I find it helps to use multipliers only, instead of mixing multiplication and division:

let path = UIBezierPath(arcCenter: trackLayer.position,
                        radius: 8,
                        startAngle: -CGFloat.pi * 0.5,
                        endAngle: CGFloat.pi * 1.5,
                        clockwise: true)

Now we know we're going from minus 90-degrees to 270-degrees ... for 360-degrees total.

Upvotes: 1

Related Questions