CAShapeLayer strokeEnd causes ring flash on tap – how to stop border flash on custom circular progress button (UIKit)?

3 weeks ago 22
ARTICLE AD BOX

I have a custom circular play/pause button in UIKit that uses CAShapeLayer as a progress ring.
Everything works except when I tap the view, the border briefly flashes even though strokeEnd = 0 and lineWidth = 0.

Here is the full code for the custom view:

@IBDesignable class CircularProgressView: UIView { private let progressLayer = CAShapeLayer() private let iconView = UIImageView() @IBInspectable var ringWidth: CGFloat = 4 @IBInspectable var ringColor: UIColor = .white @IBInspectable var playIcon: UIImage? = UIImage(named: "iconPause") @IBInspectable var pauseIcon: UIImage? = UIImage(named: "iconPlay") @IBInspectable var iconColor: UIColor = .white @IBInspectable var isPlaying: Bool = false { didSet { updateIcon() } } override init(frame: CGRect) { super.init(frame: frame) setup() } required init?(coder: NSCoder) { super.init(coder: coder) setup() } private func setup() { backgroundColor = .clear iconView.contentMode = .scaleAspectFit iconView.tintColor = iconColor addSubview(iconView) // PROGRESS RING layer.addSublayer(progressLayer) progressLayer.fillColor = UIColor.clear.cgColor progressLayer.strokeColor = ringColor.cgColor progressLayer.lineCap = .round progressLayer.strokeEnd = 0 progressLayer.lineWidth = 0 addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(viewTapped))) updateIcon() } override func layoutSubviews() { super.layoutSubviews() drawRing() iconView.frame = bounds.insetBy(dx: bounds.width * 0.28, dy: bounds.height * 0.28) } private func drawRing() { let radius = min(bounds.width, bounds.height) / 2 - ringWidth / 2 let center = CGPoint(x: bounds.midX, y: bounds.midY) let path = UIBezierPath(arcCenter: center, radius: radius, startAngle: -.pi/2, endAngle: 3 * .pi/2, clockwise: true) progressLayer.frame = bounds progressLayer.path = path.cgPath } func setProgress(_ value: CGFloat, animated: Bool = true) { let clamped = max(0, min(value, 1)) if animated { let anim = CABasicAnimation(keyPath: "strokeEnd") anim.fromValue = progressLayer.strokeEnd anim.toValue = clamped anim.duration = 0.25 progressLayer.strokeEnd = clamped progressLayer.add(anim, forKey: nil) } else { progressLayer.strokeEnd = clamped } } private func updateIcon() { let image = isPlaying ? pauseIcon : playIcon iconView.image = image?.withRenderingMode(.alwaysTemplate) iconView.tintColor = iconColor } @objc private func viewTapped() { isPlaying.toggle() // <--- PROBLEM: TAP causes the ring/border to momentarily "flash" } }

❗Problem

Even though lineWidth = 0 and strokeEnd = 0, tapping the view still causes a brief border flash at the end of the circle. It looks like Core Animation draws the stroke momentarily.

❓Question

How can I completely disable this flash and prevent the border from rendering when tapping the view? Is there a correct way to disable implicit animations or reset the layer state so the stroke never appears?

Image : enter image description here

Read Entire Article