Created
June 9, 2016 08:09
-
-
Save emadhegab/20f1e4bfd1b2c4c6423a4e15c48d3f97 to your computer and use it in GitHub Desktop.
This file contains hidden or 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 UIKit | |
@objc public protocol TimerLabelDelegate { | |
optional func countdownStarted() | |
optional func countdownFinished() | |
} | |
public extension NSTimeInterval { | |
var int: Int { | |
return Int(self) | |
} | |
} | |
public class TimerLabel: UILabel { | |
public typealias CountdownCompletion = () -> ()? | |
public typealias CountdownExecution = () -> () | |
private let defaultFireInterval = 1.0 | |
// private let date1970 = NSDate(timeIntervalSince1970: 0) | |
public var dateFormatter: NSDateFormatter { | |
let df = NSDateFormatter() | |
df.locale = NSLocale.currentLocale() | |
df.timeZone = NSTimeZone(name: "GMT") | |
df.dateFormat = timeFormat | |
return df | |
} | |
public var timeCounted: NSTimeInterval { | |
let timeCounted = NSDate().timeIntervalSinceDate(fromDate) | |
return round(timeCounted < 0 ? 0 : timeCounted) | |
} | |
public var timeRemaining: NSTimeInterval { | |
return round(currentTime) - timeCounted | |
} | |
public var isPaused: Bool { | |
return paused | |
} | |
public var isCounting: Bool { | |
return counting | |
} | |
public var isFinished: Bool { | |
return finished | |
} | |
public weak var timerLabelDelegate: TimerLabelDelegate? | |
public var timeFormat = "HH:mm:ss" | |
public var thens = [NSTimeInterval: CountdownExecution]() | |
public var countdownAttributedText: CountdownAttributedText! { | |
didSet { | |
range = (countdownAttributedText.text as NSString).rangeOfString(countdownAttributedText.replacement) | |
} | |
} | |
private var completion: CountdownCompletion? | |
private var fromDate: NSDate = NSDate() | |
private var currentDate: NSDate = NSDate() | |
private var currentTime: NSTimeInterval = 0 | |
private var diffDate: NSDate! | |
private var targetTime: NSTimeInterval = 0 | |
private var pausedDate: NSDate! | |
private var range: NSRange! | |
private var timer: NSTimer! | |
private var counting: Bool = false | |
private var endOfTimer: Bool { | |
return timeCounted >= currentTime | |
} | |
private var finished: Bool = false { | |
didSet { | |
if finished { | |
paused = false | |
counting = false | |
} | |
} | |
} | |
private var paused: Bool = false | |
// MARK: - Initialize | |
public required init?(coder aDecoder: NSCoder) { | |
super.init(coder: aDecoder) | |
} | |
override init(frame: CGRect) { | |
super.init(frame: frame) | |
} | |
public convenience init(frame: CGRect, minutes: NSTimeInterval) { | |
self.init(frame: frame) | |
setCountDownTime(minutes) | |
} | |
public convenience init(frame: CGRect, date: NSDate) { | |
self.init(frame: frame) | |
setCountDownDate(date) | |
} | |
public convenience init(frame: CGRect, fromDate: NSDate, targetDate: NSDate) { | |
self.init(frame: frame) | |
setCountDownDate(fromDate, targetDate: targetDate) | |
} | |
deinit { | |
dispose() | |
} | |
// MARK: - Setter Methods | |
public func setCountDownTime(minutes: NSTimeInterval) { | |
setCountDownTime(NSDate(), minutes: minutes) | |
} | |
public func setCountDownTime(fromDate: NSDate, minutes: NSTimeInterval) { | |
self.fromDate = fromDate | |
targetTime = minutes | |
currentTime = minutes | |
diffDate = fromDate.dateByAddingTimeInterval(minutes) | |
updateLabel() | |
} | |
public func setCountDownDate(targetDate: NSDate) { | |
setCountDownDate(NSDate(), targetDate: targetDate) | |
} | |
public func setCountDownDate(fromDate: NSDate, targetDate: NSDate) { | |
self.fromDate = fromDate | |
targetTime = targetDate.timeIntervalSinceDate(fromDate) | |
currentTime = targetDate.timeIntervalSinceDate(fromDate) | |
diffDate = fromDate.dateByAddingTimeInterval(targetTime) | |
updateLabel() | |
} | |
// MARK: - Update | |
func updateLabel() { | |
// then function execute if needed | |
thens.forEach { k, v in | |
if k.int == timeRemaining.int { | |
v() | |
thens[k] = nil | |
} | |
} | |
// update text | |
updateText() | |
// if end of timer | |
if endOfTimer { | |
text = dateFormatter.stringFromDate(fromDate.dateByAddingTimeInterval(0)) | |
timerLabelDelegate?.countdownFinished?() | |
dispose() | |
completion?() | |
} | |
} | |
} | |
// MARK: - Public | |
public extension TimerLabel { | |
func start(completion: ( () -> () )? = nil) { | |
if !isPaused { | |
// current date should be setted at the time of the counter's starting, or the time will be wrong (just a few seconds) after the first time of pausing. | |
currentDate = NSDate() | |
} | |
// pause status check | |
updatePauseStatusIfNeeded() | |
// create timer | |
updateTimer() | |
// fire! | |
timer.fire() | |
// set completion if needed | |
completion?() | |
// set delegate | |
timerLabelDelegate?.countdownStarted?() | |
} | |
} | |
// MARK: - private | |
private extension TimerLabel { | |
func updateText() { | |
guard diffDate != nil else { return } | |
// if time is before start | |
let formattedText = timeCounted < 0 | |
? dateFormatter.stringFromDate(fromDate.dateByAddingTimeInterval(0)) | |
: dateFormatter.stringFromDate(diffDate.dateByAddingTimeInterval(round(timeCounted * -1))) | |
if let countdownAttributedText = countdownAttributedText { | |
let attrTextInRange = NSAttributedString(string: formattedText, attributes: countdownAttributedText.attributes) | |
let attributedString = NSMutableAttributedString(string: countdownAttributedText.text) | |
attributedString.replaceCharactersInRange(range, withAttributedString: attrTextInRange) | |
attributedText = attributedString | |
text = attributedString.string | |
} else { | |
text = formattedText | |
} | |
setNeedsDisplay() | |
} | |
func updatePauseStatusIfNeeded() { | |
guard paused else { | |
return | |
} | |
// change date | |
let pastedTime = pausedDate.timeIntervalSinceDate(currentDate) | |
currentDate = NSDate().dateByAddingTimeInterval(-pastedTime) | |
fromDate = currentDate | |
// reset pause | |
pausedDate = nil | |
paused = false | |
} | |
func updateTimer() { | |
disposeTimer() | |
// create | |
timer = NSTimer.scheduledTimerWithTimeInterval(defaultFireInterval, | |
target: self, | |
selector: #selector(updateLabel), | |
userInfo: nil, | |
repeats: true) | |
// register to NSrunloop | |
NSRunLoop.currentRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes) | |
counting = true | |
} | |
func disposeTimer() { | |
if timer != nil { | |
timer.invalidate() | |
timer = nil | |
} | |
} | |
func dispose() { | |
// reset | |
pausedDate = nil | |
// invalidate timer | |
disposeTimer() | |
// stop counting | |
finished = true | |
} | |
} | |
public class CountdownAttributedText: NSObject { | |
private let text: String | |
private let replacement: String | |
private let attributes: [String: AnyObject]? | |
public init(text: String, replacement: String, attributes: [String: AnyObject]? = nil) { | |
self.text = text | |
self.replacement = replacement | |
self.attributes = attributes | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment