Search code examples
macostimernstimerbackground-process

Swift: Keep NSTimer Running While Computer is Asleep (OSX)


I am writing an OS X app that includes a generic stopwatch timer. I'm using NSTimer. I'd like the user to be able to start the timer and come back to it after a long time (say, 30 minutes), and the timer would still be running. The problem is that my timer does not continue running while the computer is closed or asleep, and I don't want to keep my computer open and on for really long periods of time. There are several threads about this problem concerning iOS apps, but none (at least that I've found) pertaining to OS X. Does anyone know of a workaround for this issue? As an example, I'm trying to mimic the "Stopwatch" functionality of the "Clock" app that comes with iOS, except with a laptop instead of a phone. The stopwatch in the "clock" app will continue running even when the phone is off for extended periods of time.


Solution

  • The way I figured out to do this was not to actually run the NSTimer in the background, but rather to find out how much time had elapsed between when the app goes into the background and when it comes back into focus. Using the delegate methods applicationWillResignActive: and applicationWillBecomeActive: of NSApplicationDelegate:

    let timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: #selector(self), userInfo: nil, repeats: true)
    var resignDate: NSDate?
    var stopwatch = 0
    
    func update() {
        stopwatch += 1
    }
    
    func applicationWillResignActive(notification: NSNotification) {
        timer.invalidate()
        resignDate = NSDate() // save current time 
    }
    
    func applicationWillBecomeActive(notification: NSNotification) {
        if resignDate != nil {
            let timeSinceResign = NSDate().timeIntervalSinceDate(resignDate!))
            stopwatch += Int(timeSinceResign)
            timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: #selector(self), userInfo: nil, repeats: true)
            resignDate = nil
        }
    }
    

    applicationWillResignActive: will get called every time the app goes out of focus. When this happens, I save the current date (NSDate()) in a variable called resignDate. Then, when the application is reactivated (after who knows how long; it doesn't matter) applicationWillBecomeActive: is called. Then, I take another NSDate value that is the current time, and I find the amount of time between the current time and resignDate. After adding this amount of time to my time value, I can revalidate and the NSTimer so it keeps going.