Timer.scheduledTimer not firing - swift

I am trying to calculate the time it takes a vehicle to go between two checkpoints. I have setup the following timer to achieve this.
func startTimer() {
if hasStarted == true && timerStarted == false {
print("Timing started!")
gameTimer = Timer.scheduledTimer(timeInterval: 0.001, target: self, selector: (#selector(activeTiming)), userInfo: nil, repeats: true)
timerStarted = true
}
}
#objc func activeTiming() {
print("Active timing block")
if(hasFinished == false) {
gameTime = gameTime + 0.001
print("Add time succeeded")
} else {
gameTimer?.invalidate()
}
}
The expected output would be the following:
Timing started!
Active timing block
Add time succeeded
Add time succeeded ... etc
The actual output:
Timing started!
So it would appear that the startTimer is properly being called but the timer is not firing the activeTiming block of code. Any suggestions would be extremely helpful. Thank you for your help in advance.

Posting this code as it is what I'm using, but I’m no expert on Swift, so your mileage may vary!
class PerformanceTest {
var name: String = ""
var tolerance: Int64 = 0
var lastTime: Int64 = 0
var thisTime: Int64 = 0
var delta: Int64 = 0
var percent: Float = 0
func setTolerance(vName: String, vTolerance: Int64) {
name = vName
tolerance = vTolerance
}
func reset() {
delta = 0
percent = Float((Float(delta) / Float(tolerance))) * 100
//high = 0
}
func start() {
lastTime = Date().toMillis()
}
func finish() {
thisTime = Date().toMillis()
let vDelta = thisTime - lastTime
if(vDelta > delta) {
delta = vDelta
percent = Float((Float(delta) / Float(tolerance))) * 100
if(delta > tolerance) {
print("Performance Indicator: \(name) Above Tolerance" + String(format: "%3.0f", percent) + "%")
}
}
}
func display() -> String {
//high = delta
//print("\(vString) Tolerance: \(tolerance) Max: \(high)")
return String(format: "%3.0f", percent) + "% |"
}
}
extension Date {
func toMillis() -> Int64! {
return Int64(self.timeIntervalSince1970 * 1000)
}
Usage:
var performanceDefenseLoop = PerformanceTest()
performanceDefenseLoop.setTolerance(vName: "DefenseLoop", vTolerance: 150)
func timeToUpdateDefenses()
{
performanceDefenseLoop.start()
defesensesLoop()
performanceDefenseLoop.finish()
print("\(performanceDefenseLoop.Display())"
}
// To reset
performanceDefenseLoop.reset()
Be sure to start timers in the main thread!

I also faced a similar issue where the timer body was not getting called. I found out that the timer was getting scheduled from a background thread instead of the main thread. Wrapping it in DispatchQueue.main.async fixed it.
DispatchQueue.main.async {
self.pingTimer = Timer.scheduledTimer(withTimeInterval: timerInterval, repeats: true, block: { [weak self] timer in
//
Logger.shared.log(.anyCable, .debug, "timer run")
self?.sendPing(sender: self)
})
}

Your code works for me, but that isn't how you want to time an event because the Timer isn't that accurate, and you are wasting a lot of computing time (read battery).
Instead, I suggest the following approach:
When the event starts, record the start time:
let startTime = Date()
When the event ends, compute the elapsed time:
let elapsedTime = Date().timeIntervalSince(startTime)
elapsedTime will be in seconds (including fractional seconds).

Related

Timer fires twice then doesn't reset

I've got a countdown timer that counts down and executes the code twice but then it doesn't reset, instead it continues counting in negative numbers. Can someone tell me why?
var didCount = 4
func startDelayTimer() {
delayTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { _ in
self.startDelayCount()
})
}
func startDelayCount() {
delayTime -= 1
timeLbl.text = String(delayTime)
if delayTime <= 3 {
soundPLayer.play()
}
if delayTime == 0 {
delayTimer.invalidate()
doSomething()
}
}
func doSomething() {
doCount += 1
if doCount < didCount {
startDelayTimer()
}
else {
print("done")
}
}
The direct issue is that you reset the timer without remebering to reset delayTime.
But I think there's also an architectural issue, in that you have a murky mix of responsibilities (managing a timer, updating a label, and playing sounds). I'd suggest you extract the timer responsibilities elsewhere.
Perhaps something along these lines:
/// A timer which counts from `initialCount` down to 0, firing the didFire callback on every count
/// After each full countdown, it repeats itself until the repeatLimit is reached.
class RepeatingCountDownTimer {
typealias FiredCallback: () -> Void
typealias FinishedCallback: () -> Void
private var initialCount: Int
private var currentCount: Int // Renamed from old "delayTime"
private var repeatCount = 0 // Renamed from old "doCount"
private let repeatLimit: Int // Renamed from old "didCount"
private var timer: Timer?
private let didFire: FiredCallback
private let didFinish: FinishedCallback
init(
countDownFrom initialCount: Int,
repeatLimit: Int,
didFire: #escaping FiredCallback,
didFinish: #escaping FinishedCallback
) {
self.initialCount = initialCount
self.currentCount = initialCount
self.repeatLimit = repeatLimit
self.didFire = didFire
self.didFinish = didFinish
}
public func start() {
self.currentCount = self.initialCount
self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { [weak self] in
self?.fire()
})
}
private func fire() {
currentCount -= 1
self.didFire(currentCount)
if currentCount == 0 {
repeat()
}
}
private func repeat() {
repeatCount += 1
if repeatCount < repeatLimit {
self.timer?.invalidate()
start()
} else {
finished()
}
}
private func finished() {
self.timer?.invalidate()
self.timer = nil
self.didFinish()
}
}
That's just rough psuedo-code, which will certainly need tweaking. But the idea is to separate timer and state management from the other things you need to do. This should make it easier to debug/develop/test this code, replacing useless names like doSomething with more concretely named events.
The usage might look something like:
let countDownTimer = RepeatingCountDownTimer(
countDownFrom: 4,
repeatLimit: 4,
didFire: { count in
timeLbl.text = String(count)
soundPlayer.play()
},
didFinish: { print("done") }
)
countDownTimer.start()

Label with Timer not Stopping

I am creating a lap stopwatch, and I am having trouble stopping the timer when the "STOP" button is clicked. I am able to get the text to reset to 0, but the timer keeps running and if I hit start again the timer is running as if I never hit stop.
Timer code:
func goTimer()
{
if timer == nil {
timer = Timer.scheduledTimer(timeInterval : 0.1,
target : self,
selector :#selector(timerAction(_:)),
userInfo: nil, repeats: true);
}
}
func stopTimer()
{
if timer != nil {
timer!.invalidate()
timer = nil
}
}
func updateTimer() {
let intervalTotal = -Int(startDate.timeIntervalSinceNow)
let hours = intervalTotal / 3600
let minutes = intervalTotal / 60 % 60
let seconds = intervalTotal % 60
if startButton.currentTitle == "STOP" {
totalTime.text = String(format: "%02i:%02i:%02i", hours, minutes, seconds)
lapTime.text = String(format: "%02i:%02i:%02i", hours, minutes, seconds)
}
}
#objc func timerAction (_ timer : Timer) {
print("timerAction(_:)")
self.updateTimer()
}
#IBAction func startTimer(_ sender: Any) {
if startButton.currentTitle == "STOP" {
startButton.backgroundColor = UIColor.systemGreen
startButton.setTitle("START", for : .normal)
stopTimer()
lapTime.text = "00:00:00"
}
else if startButton.currentTitle == "START" {
startButton.backgroundColor = UIColor.systemRed
startButton.setTitle("STOP", for : .normal)
goTimer()
lapButton.isEnabled = true
lapButton.backgroundColor = UIColor.systemBlue
}
}
let intervalTotal = -Int(startDate.timeIntervalSinceNow)
I don't see you resetting that startDate in your stopTimer() function.
you have a variable
var startDate: Date = Date()
somewhere on top of your code. If its not a variable (if its let) change it to variable. You want to set this value when you start your timer. so
func goTimer() {
// Add this to set the date to when your function starts
startDate = Date()
....
}
That's because when you're setting the value in your label here
func updateTimer() {
let intervalTotal = -Int(startDate.timeIntervalSinceNow)
....
}
you're setting the value from startDate until now

Unit Testing a Timer?

I want to test that a signal is getting fired every 6 seconds. My code looks like this:
class DataStore {
var clinics: Int { return clinicsSignal.lastDataFired ?? 0 }
let clinicsSignal = Signal<Int>(retainLastData: true)
var timer: Timer?
init() {
}
func load() {
self.clinicsSignal.fire(0)
DispatchQueue.main.async { [weak self] in
self!.timer?.invalidate()
self!.timer = Timer.scheduledTimer(withTimeInterval: 6.0, repeats: true) { [weak self] _ in
self?.clinicsSignal.fire(9)
}
}
}
}
My test code looks like this:
func testRefresh() {
var dataStore: DataStore = DataStore()
dataStore.clinicsSignal.subscribeOnce(with: self) {
print("clinics signal = \($0)")
dataStore.clinicsSignal.fire(0)
}
dataStore.load()
sleep(30)
print("clinics3 = \(dataStore.clinics)")
}
When I sleep for 30 seconds, the timer code doesn't get ran again until after the 30 seconds, therefore it's not getting ran once every 6 seconds like it's supposed to. Any idea on how to test that code in a timer is gettin hit at specific times? Thanks.
The sleep function blocks your thread and timers are associated to threads.
You should use expectation.
func testRefresh() {
var dataStore: DataStore = DataStore()
let expec = expectation(description: "Timer expectation") // create an expectation
dataStore.clinicsSignal.subscribeOnce(with: self) {
print("clinics signal = \($0)")
dataStore.clinicsSignal.fire(0)
expec.fulfill() // tell the expectation that everything's done
}
dataStore.load()
wait(for: [expec], timeout: 7.0) // wait for fulfilling every expectation (in this case only one), timeout must be greater than the timer interval
}

Swift : Pause and Resume NSTimer

I know this has been posted before, but I'm new to swift and i would like to find help as per my situation.
So i need to run a timer, and when the stopMusic() is called(somewhere in my app), i need to pause the timer, then when the playMusic() is called, i need to resume the timer.
Here is what i have:
override func viewDidLoad() {
runFirstDropTimer = NSTimer.scheduledTimerWithTimeInterval(38.2, target: self, selector: "runAnimation", userInfo: nil, repeats: false)
}
func stopMusic() {
//Pause Timer
}
func playMusic() {
//Resume Timer
}
Thank you for any help you can provide!!!
Swift 5.
Resumable timer:
class ResumableTimer: NSObject {
private var timer: Timer? = Timer()
private var callback: () -> Void
private var startTime: TimeInterval?
private var elapsedTime: TimeInterval?
// MARK: Init
init(interval: Double, callback: #escaping () -> Void) {
self.callback = callback
self.interval = interval
}
// MARK: API
var isRepeatable: Bool = false
var interval: Double = 0.0
func isPaused() -> Bool {
guard let timer = timer else { return false }
return !timer.isValid
}
func start() {
runTimer(interval: interval)
}
func pause() {
elapsedTime = Date.timeIntervalSinceReferenceDate - (startTime ?? 0.0)
timer?.invalidate()
}
func resume() {
interval -= elapsedTime ?? 0.0
runTimer(interval: interval)
}
func invalidate() {
timer?.invalidate()
}
func reset() {
startTime = Date.timeIntervalSinceReferenceDate
runTimer(interval: interval)
}
// MARK: Private
private func runTimer(interval: Double) {
startTime = Date.timeIntervalSinceReferenceDate
timer = Timer.scheduledTimer(withTimeInterval: interval, repeats: isRepeatable) { [weak self] _ in
self?.callback()
}
}
}
Usage:
private var playTimer: ResumableTimer?
playTimer = ResumableTimer(interval: 5.0) { [weak self] in
self?.doSomethingOnTimerFire()
}
playTimer?.start()
Short answer: You can't. Timers can't be paused.
Instead you need to record the current time when you start the timer:
let startTime = NSDate.timeIntervalSinceReferenceDate()
let interval = 38.2
//Start your timer
runFirstDropTimer = NSTimer.scheduledTimerWithTimeInterval(interval,
target: self,
selector: "runAnimation",
userInfo: nil,
repeats: false)
Then, when you want to pause your timer, invalidate it and figure out how much time is left:
runFirstDropTimer.invalidate()
let elapsed = NSDate.timeIntervalSinceReferenceDate() - startTime
let remaining = interval - elapsed
And finally when you want to resume your timer create a new timer with the remaining value.

How to loop NSTimer in SWIFT with a specific number of counting

I am trying to loop a timer with a specific number of counting...
eg... 1, 2, 3, 4, 5
then loop again, the same counting, 10 times, then stop.
I was able to do the counting, but I can't do the looping of the counting.
What am I doing wrong?
var times = 0
var stopCounting = 10
#IBAction func buttonPressed(sender: UIButton) {
self.startTimer()
}
func startTimer(){
self.timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: Selector("startCounting"), userInfo: nil, repeats: true)
}
func startCounting (){
times += 1
if times < stopCounting + 1{
if counter > -1 && counter < 6 {
counting.text = String(counter++)
} else if counter == Int(counting.text) {
counting.text = "0"
}
}
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var strTime: UILabel!
var timer = NSTimer()
var endTime: NSTimeInterval = 0
var now: NSTimeInterval { return NSDate().timeIntervalSinceReferenceDate }
var times = 0
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func updateText(){
let time = Int(ceil(endTime - now))
if time == 0 {
times++
if times == 10 {
strTime.text = "end"
timer.invalidate()
}
endTime = NSDate().timeIntervalSinceReferenceDate + 5
} else {
strTime.text = time.description
}
}
#IBAction func buttonPressed(sender: UIButton) {
timer = NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector: "updateText", userInfo: nil, repeats: true)
endTime = NSDate().timeIntervalSinceReferenceDate + 5
sender.hidden = true
}
}
to make it count from 1 to 5 you need to change from ceil to floor, abs() and add +1
func updateText(){
let time = Int(floor(abs(endTime - now - 5)))
if time == 5 {
times++
if times == 10 {
strTime.text = "10" + " " + "0"
timer.invalidate()
}
endTime = NSDate().timeIntervalSinceReferenceDate + 5
} else {
strTime.text = times.description + " " + (time+1).description
}
}
Never done swift, but took a stab in the dark, why not.
var loops = 5 // Number of times to loop the counting
var countTo = 10 // Number to count to
var loopCount = 0 // Number of loops
#IBAction func buttonPressed(sender: UIButton) {
self.timer = NSTimer.scheduledTimerWithTimeInterval(1.0,
target: self,
selector: Selector("startCounting"),
userInfo: nil,
repeats: true)
}
func startCounting (){
// Loop from 1 to countTo, setting the text for each number
for i in 1..countTo {
counting.text = String(i)
}
// Done counting, reset to zero
counting.text = "0"
// Update the number of times the counting has run
loopCount++
// If have completed all the loops, invalidate the timer
if (loopCounter >= loops) {
self.timer.invalidate()
}
}