Matt Quiros Matt Quiros - 1 year ago 39
Swift Question

NSTimer stops when view controller is not the selected tab or not showing

I have a strange problem with my countdown timer. It fires off normally when my start button is hit, and is reinstantiated correctly when I close the app and relaunch it again. However, when I select a different tab and stay there for a while, it stops counting down, then resumes counting down from where it left off when I show the countdown tab again.

For example, if the timer is now at

(format is HH:MM:SS), select some other tab, stay there for 5 minutes, and then go back to the timer tab, it's only at the
mark. When I close the app (double tap the home button, swipe up my app) and reopen it, it starts off at a more reasonable

I've posted the relevant code from the class to show how I'm setting up the timer, but a summary of what it does:

  • I have plus (+) and minus (-) buttons somewhere that, when tapped, call

  • recalculate()
    fires off a

  • A
    computes for the starting HH:MM:ss based on the addition/removal of a new record. The
    of a
    executes in the main thread.

  • A
    creates the
    in the
    if the
    hasn't been created yet.

  • The
    every 1 second. It reduces the
    by 1 second by calling


class CalculatorViewController: MQLoadableViewController {

let calculationQueue: NSOperationQueue // Initialized in init()
var calculation: Calculation?
var countdownTimer: NSTimer?

func recalculate() {
if let profile = AppState.sharedState.currentProfile {
// Cancel all calculation operations.

let calculateOperation = self.createCalculateOperation(profile)

func decayCalculation() {
if let calculation = self.calculation {
// tick() subtracts 1 second from the timer and adjusts the
// hours and minutes accordingly. Returns true when the timer
// goes down to 00:00:00.
let timerFinished = calculation.timer.tick()

// Pass the calculation object to update the timer label
// and other things.
if let mainView = self.primaryView as? CalculatorView {
mainView.calculation = calculation

// Invalidate the timer when it hits 00:00:00.
if timerFinished == true {
if let countdownTimer = self.countdownTimer {

func createCalculateOperation(profile: Profile) -> CalculateOperation {
let calculateOperation = CalculateOperation(profile: profile)

calculateOperation.successBlock = {[unowned self] result in
if let calculation = result as? Calculation {
self.calculation = calculation

/* Hide the loading screen, show the calculation results, etc. */

// Create the NSTimer.
if self.countdownTimer == nil {
self.countdownTimer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("decayCalculation"), userInfo: nil, repeats: true)

return calculateOperation


Answer Source

Well, if I leave the app in some other tab and not touch the phone for a while, it eventually goes to sleep, the app resigns active, and enters the background, which stops the timer.

The solution was to set my view controller as a listener to the UIApplicationWillEnterForegroundNotification and call recalculate to correct my timer's countdown value.