12

I have a timer that runs to restart an alarm once it goes off.

alarm = NSTimer.scheduledTimerWithTimeInterval(
    60 * minutesConstant + secondsConstant,
    target:self,
    selector: Selector("endSession"),
    userInfo: nil,
    repeats:false)

Its selector function sets a mode flag, and calls the original function that set the alarm with new minutes and seconds constants, and also sends a user notification that the session has restarted.

I have a menu item that gets updated with the remaining time

enter image description here

So I've been opening it to check that my alarm does indeed restart, and that the notification shows once it hits zero. It works, but when I have the menu open and it gets down to zero it just stays at 0:00 and the timer doesn't fire until I click off the menu at which point it immediately shows the notification and resets the timer like intended.

How can I force the timer to fire when the menu is open? It's not a big deal but I don't want the user to be confused with the session just hangs if they watch the timer go down.

Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
Spencer Wood
  • 610
  • 6
  • 15

2 Answers2

26

You just have to add your timer to the main runloop as follow:

Swift 4.2

RunLoop.main.add(timer, forMode: .common)

Swift 3

RunLoop.main.add(alarm, forMode: .commonModes)

Swift 2.x

NSRunLoop.mainRunLoop().addTimer(alarm, forMode: NSRunLoopCommonModes)
Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
  • 1
    This is correct. It's basically what you can see in the airport menu as it periodically scans for SSIDs – uchuugaka Jan 17 '15 at 08:32
  • 1
    In particular, the run loop is running in the `NSEventTrackingRunLoopMode` during menu tracking. If your timer is only in the `NSDefaultRunLoopMode`, it will not fire. Both of those modes are in the `NSRunLoopCommonModes` set, so adding the timer to that pseudo-mode allows it to fire in both cases (and others). – Ken Thomases Jan 17 '15 at 09:40
  • Perfect, thanks for the response and clear explanation! – Spencer Wood Jan 17 '15 at 11:50
  • In Swift 3 it's correct solution: `RunLoop.main.add(timer, forMode: RunLoopMode.commonModes)` – Anton Danilchenko Oct 12 '16 at 13:33
  • 2
    I have experimenting with my app (I have exported it and ran as a normal app). I have found that it's incorrect work with timer when I leave my app in the background running - it starts to work with delay, and after some time - freezes. It looks like that this solution doesn't work. – Anton Danilchenko Oct 12 '16 at 15:04
  • You mean your solution doesn't work. Feel free to open a new question showing your code, how you are measuring time, what you are expecting and the results you are getting. – Leo Dabus Oct 12 '16 at 15:16
0

I had the same problem, but in MacOS using NSOpenPanel. My problem was a timer used for TCP keep-alive which would not fire, and I would lose connection with my devices if the panel was open too long. My app would reconnect once I closed the panel, but was now in the wrong state. Just adding the code as recommended above fixed the problem.

RunLoop.main.add(timer, forMode: .common)
grapeffx
  • 171
  • 1
  • 5