You can create a custom class of UIView
and add layer to it.
class CircularProgressBar: UIView {
private var circularPath: UIBezierPath = UIBezierPath()
var progressLayer: CAShapeLayer!
var progress: Double = 0 {
willSet(newValue) {
progressLayer?.strokeEnd = CGFloat(newValue)
}
}
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
override func layoutSubviews() {
super.layoutSubviews()
removeAllSubviewAndSublayers()
setupCircle()
}
private func removeAllSubviewAndSublayers() {
layer.sublayers?.forEach { $0.removeFromSuperlayer() }
subviews.forEach { $0.removeFromSuperview() }
}
func setupCircle() {
let x = self.frame.width / 2
let y = self.frame.height / 2
let center = CGPoint(x: x, y: y)
circularPath = UIBezierPath(arcCenter: center, radius: x, startAngle: -0.5 * CGFloat.pi, endAngle: 1.5 * CGFloat.pi, clockwise: true)
progressLayer = CAShapeLayer()
progressLayer.path = circularPath.cgPath
progressLayer.strokeColor = UIColor.white.cgColor
progressLayer.fillColor = UIColor.clear.cgColor
progressLayer.lineWidth = x/10
progressLayer.lineCap = .round
progressLayer.strokeEnd = 0
layer.addSublayer(progressLayer)
}
func addStroke(duration: Double = 2.0) {
let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.fromValue = 0.0
animation.toValue = 1.0
animation.duration = duration
animation.beginTime = CACurrentMediaTime()
progressLayer.add(animation, forKey: "strokeEnd")
}
func removeStroke(duration: Double = 0.0) {
let revAnimation = CABasicAnimation(keyPath: "strokeEnd")
revAnimation.duration = duration
revAnimation.fromValue = progressLayer.presentation()?.strokeEnd
revAnimation.toValue = 0.0
progressLayer.removeAllAnimations()
progressLayer.add(revAnimation, forKey: "strokeEnd")
}
}
In UIViewController
create a UIImageView
and CircularProgressBar
. Set isUserInteractionEnabled
to true
of imageView
and add progressView
to it.
In viewDidLayoutSubviews()
method set the frame
of progressView equal to bounds
of imageView
. You also need to set Timer
to execute action. Here is the full code.
class ViewController: UIViewController {
let imageView = UIImageView(image: UIImage(named: "icon_sos")!)
let progressView = CircularProgressBar()
var startTime: Date?
var endTime: Date?
var longPress: UILongPressGestureRecognizer?
var timer: Timer?
let longPressDuration: Double = 2.0
override func viewDidLoad() {
super.viewDidLoad()
longPress = UILongPressGestureRecognizer(target: self, action: #selector(longPressAction(_:)))
longPress?.minimumPressDuration = 0.01
imageView.frame = CGRect(x: 100, y: 100, width: 30, height: 30)
imageView.isUserInteractionEnabled = true
imageView.addGestureRecognizer(longPress!)
imageView.addSubview(progressView)
self.view.addSubview(imageView)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
progressView.frame = imageView.bounds
}
private func setupTimer() {
timer = Timer.scheduledTimer(timeInterval: self.longPressDuration, target: self, selector: #selector(fireTimer), userInfo: nil, repeats: false)
}
@objc private func fireTimer() {
longPress?.isEnabled = false
longPress?.isEnabled = true
progressView.removeStroke()
if let timer = timer {
timer.invalidate()
self.timer = nil
}
// execute button action here
print("Do something")
}
@objc private func longPressAction(_ sender: UILongPressGestureRecognizer) {
if sender.state == .began {
print("Long Press Began: ", Date())
startTime = Date()
self.progressView.addStroke(duration: self.longPressDuration)
setupTimer()
}
if sender.state == .changed {
print("Long Press Changed: ", Date())
}
if sender.state == .cancelled {
print("Long Press Cancelled: ", Date())
endTime = Date()
if let startTime = startTime, let endTime = endTime {
let interval = DateInterval(start: startTime, end: endTime)
progressView.removeStroke(duration: interval.duration.magnitude)
if interval.duration.magnitude < self.longPressDuration {
timer?.invalidate()
timer = nil
}
}
}
if sender.state == .ended {
print("Long Press Ended: ", Date())
endTime = Date()
if let startTime = startTime, let endTime = endTime {
let interval = DateInterval(start: startTime, end: endTime)
progressView.removeStroke(duration: interval.duration.magnitude)
if interval.duration.magnitude < self.longPressDuration {
timer?.invalidate()
timer = nil
}
}
}
}
}