Skip to content

Instantly share code, notes, and snippets.

@dimitris-c
Created July 23, 2018 08:19
Show Gist options
  • Save dimitris-c/3ebf970c3fd169cf11db7acbd46fdc8f to your computer and use it in GitHub Desktop.
Save dimitris-c/3ebf970c3fd169cf11db7acbd46fdc8f to your computer and use it in GitHub Desktop.
import Foundation
import UIKit
import RxSwift
import RxCocoa
final class CircularCountdownView: UIView {
let disposeBag = DisposeBag()
private var progressView = UIView()
private var progressLayer: CAShapeLayer = {
let layer = CAShapeLayer()
layer.lineCap = kCALineCapRound
layer.fillRule = kCALineJoinRound
layer.fillColor = UIColor.clear.cgColor
return layer
}()
public var progressStrokeColor: UIColor = .white {
didSet {
progressLayer.strokeColor = progressStrokeColor.cgColor
self.layoutIfNeeded()
}
}
public var progressLineWidth: CGFloat = 3 {
didSet {
progressLayer.lineWidth = progressLineWidth
self.layoutIfNeeded()
}
}
public let totalTime = BehaviorRelay<Int>(value: 0)
public var finished: Observable<Bool> {
return finishedSubject.asObservable()
}
private var finishedSubject = PublishSubject<Bool>()
private var secondsElapsed: TimeInterval = 0
private var totalTimeValue: TimeInterval {
return TimeInterval(self.totalTime.value)
}
override init(frame: CGRect) {
super.init(frame: frame)
self.setupUI()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
let lineWidthExpaded = progressLineWidth * 2
let expandedWidth = self.boundsWidth + lineWidthExpaded
let expandedHeight = self.boundsHeight + lineWidthExpaded
let size = CGSize(width: expandedWidth, height: expandedHeight)
self.progressView.frameSize = size
self.progressView.center = self.ownCenter
self.progressLayer.frame.size = size
}
func setupUI() {
self.addSubview(progressView)
self.progressView.layer.addSublayer(progressLayer)
self.progressLayer.lineWidth = progressLineWidth
self.progressLayer.strokeColor = progressStrokeColor.cgColor
}
private func updateProgressLayer(progress: CGFloat) {
let sizeCenter = CGPoint(x: self.progressView.frameWidth * 0.5,
y: self.progressView.frameHeight * 0.5)
let radius = (self.progressView.frameWidth - progressLineWidth) * 0.5
let startAngle = -(CGFloat.pi / 2)
let endAngle = (progress * CGFloat.pi * 2) - CGFloat.pi / 2
let path = UIBezierPath(arcCenter: sizeCenter,
radius: radius,
startAngle: startAngle,
endAngle: endAngle,
clockwise: true)
progressLayer.path = path.cgPath
}
fileprivate var displayLink: CADisplayLink?
func start() {
secondsElapsed = 0
displayLink?.invalidate()
displayLink = UIScreen.main.displayLink(withTarget: self, selector: #selector(CircularCountdownView.tick))
displayLink!.add(to: RunLoop.current, forMode: RunLoopMode.defaultRunLoopMode)
}
func stop() {
displayLink?.invalidate()
displayLink = nil
}
@objc func tick() {
secondsElapsed += displayLink!.duration
let progress: CGFloat = CGFloat(secondsElapsed) / CGFloat(totalTimeValue)
self.updateProgressLayer(progress: progress)
if progress >= 1.0 {
stop()
finishedSubject.onNext(true)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment