I’ve a customized round play/pause button in UIKit that makes use of CAShapeLayer as a progress ring.
Every little thing works besides after I faucet the view, the border briefly flashes regardless that strokeEnd = 0 and lineWidth = 0.
Right here is the total code for the customized view:
@IBDesignable
class CircularProgressView: UIView {
non-public let progressLayer = CAShapeLayer()
non-public 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(body: CGRect) {
tremendous.init(body: body)
setup()
}
required init?(coder: NSCoder) {
tremendous.init(coder: coder)
setup()
}
non-public 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 = .spherical
progressLayer.strokeEnd = 0
progressLayer.lineWidth = 0
addGestureRecognizer(UITapGestureRecognizer(goal: self, motion: #selector(viewTapped)))
updateIcon()
}
override func layoutSubviews() {
tremendous.layoutSubviews()
drawRing()
iconView.body = bounds.insetBy(dx: bounds.width * 0.28, dy: bounds.top * 0.28)
}
non-public func drawRing() {
let radius = min(bounds.width, bounds.top) / 2 - ringWidth / 2
let heart = CGPoint(x: bounds.midX, y: bounds.midY)
let path = UIBezierPath(arcCenter: heart, radius: radius,
startAngle: -.pi/2, endAngle: 3 * .pi/2, clockwise: true)
progressLayer.body = bounds
progressLayer.path = path.cgPath
}
func setProgress(_ worth: CGFloat, animated: Bool = true) {
let clamped = max(0, min(worth, 1))
if animated {
let anim = CABasicAnimation(keyPath: "strokeEnd")
anim.fromValue = progressLayer.strokeEnd
anim.toValue = clamped
anim.length = 0.25
progressLayer.strokeEnd = clamped
progressLayer.add(anim, forKey: nil)
} else {
progressLayer.strokeEnd = clamped
}
}
non-public func updateIcon() {
let picture = isPlaying ? pauseIcon : playIcon
iconView.picture = picture?.withRenderingMode(.alwaysTemplate)
iconView.tintColor = iconColor
}
@objc non-public func viewTapped() {
isPlaying.toggle()
// <--- PROBLEM: TAP causes the ring/border to momentarily "flash"
}
}
❗Downside
Regardless that lineWidth = 0 and strokeEnd = 0, tapping the view nonetheless causes a short border flash on the finish of the circle. It seems to be like Core Animation attracts the stroke momentarily.
❓Query
How can I fully disable this flash and stop the border from rendering when tapping the view?
Is there an accurate solution to disable implicit animations or reset the layer state so the stroke by no means seems?


