CABasicAnimation描边进度异常:字符计数进度条仅达70%求助
字符计数圆环进度无法满值的问题排查与修复
问题描述
我实现了一个类似Twitter的自定义字符计数进度圆环,通过CABasicAnimation的strokeEnd属性实现描边动画。但存在计算逻辑问题:当字符数达到上限280时,描边进度仅约70%,无法达到预期的100%。
实现代码
CharacterCountView 类
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) } }
文本变化处理方法
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) }
问题原因
核心问题出在圆环路径的角度范围设置错误:
- 当前代码创建的贝塞尔路径,
startAngle为-CGFloat.pi/2(对应12点钟方向),endAngle为2*CGFloat.pi(对应3点钟方向),顺时针绘制的总角度跨度是2.5 * CGFloat.pi(一圈加四分之一圈)。 strokeEnd的取值范围0-1对应路径的总长度比例,所以当传入1(280字符时的percentage=1),实际只走完了这个超长路径的全部,但其中只有2π / 2.5π = 0.8(即80%)的部分是完整圆环的范围,剩余四分之一圈超出了圆环可视区域,导致视觉上进度仅约70%-80%。
修复方案
1. 修正圆环路径的角度范围
将路径的endAngle改为3*CGFloat.pi/2,这样从-π/2到3π/2顺时针绘制,总角度跨度正好是2π(完整一圈),此时strokeEnd=1就对应完整的圆环描边:
// 修改 setUpView 中的路径创建代码 let path = UIBezierPath(arcCenter: trackLayer.position, radius: 8, startAngle: -CGFloat.pi / 2, endAngle: 3 * CGFloat.pi / 2, clockwise: true)
2. 修复手势响应方法的参数错误
当前手势绑定的animateToValue方法参数不匹配,点击时会崩溃,修正为:
@objc func animateToValue(_ gesture: UITapGestureRecognizer) { // 示例:点击时测试满进度 let testValue = 1.0 let animation = CABasicAnimation(keyPath: "strokeEnd") animation.toValue = testValue animation.duration = 0.5 animation.fillMode = .forwards animation.isRemovedOnCompletion = false progressLayer.add(animation, forKey: "strokeAnimation") progressLayer.strokeEnd = testValue if testValue > 1 { print("100% REACHED") } }
3. 优化字符数超过上限的处理
当字符数超过280时,限制进度比例不超过1,并可添加样式提示:
func textDidChange(to value: String) { postBarButtonItem.isEnabled = !value.isEmpty && value.count <= 280 let characterCount = Double(value.count) var percentage = characterCount / 280 // 限制最大比例为1,避免进度超出圆环 percentage = min(percentage, 1.0) print(percentage) createPostInputAccessoryView.characterCountView.animateToValue(value: percentage) // 可选:超过字符数时将圆环改为红色 if value.count > 280 { createPostInputAccessoryView.characterCountView.progressLayer.strokeColor = UIColor.systemRed.cgColor } else { createPostInputAccessoryView.characterCountView.progressLayer.strokeColor = Color.accent.cgColor } }
内容的提问来源于stack exchange,提问作者David Henry
相关产品推荐
相关产品推荐

