Matt Quiros Matt Quiros - 6 months ago 9
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

00:28:00
(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
27:52
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
22:50
mark.

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()
    .

  • recalculate()
    fires off a
    CalculateOperation
    .

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

  • A
    CalculateOperation
    creates the
    NSTimer
    in the
    successBlock
    if the
    countdownTimer
    hasn't been created yet.

  • The
    NSTimer
    executes
    decayCalculation()
    every 1 second. It reduces the
    calculation.timer
    by 1 second by calling
    tick()
    .



Code:

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.
self.calculationQueue.cancelAllOperations()

let calculateOperation = self.createCalculateOperation(profile)
self.calculationQueue.addOperation(calculateOperation)
}
}

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 {
countdownTimer.invalidate()
}
}
}
}

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

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.

Comments