Created
July 23, 2018 08:19
-
-
Save dimitris-c/3ebf970c3fd169cf11db7acbd46fdc8f to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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