CountDown Timer running on background

Hello guys,
I’m noob doing apps for iOS and I’m creating an app, Pomodoro timer in iOS. But the timer is not running in the background or when I lock the phone in simulation.
I read about using this function UIApplication.shared.beginBackgroundTask it works at the beginning and sends a notification when the timer is done, the work time, but when is the rest time is not working. Also, I use the RunLoop function

Do you know if I use the wrong functions or is another way to do the timer?

This is my code.

//
//  ViewController.swift
//  Pomodoro
//
//  Created by Daniel Flores on 23/06/20.
//  Copyright © 2020 Daniel Flores. All rights reserved.
//

import UIKit


class ViewController: UIViewController {
    
    @IBOutlet weak var getCurrentDate: UILabel!
    @IBOutlet weak var getTimerUpdate: UILabel!
    @IBOutlet weak var buttonStartOrPause: UIButton!
    @IBOutlet weak var totalRoundsCount: UILabel!
    @IBOutlet weak var progressTimeBar: UIProgressView!
    
    let pauseImage = UIImage(systemName: "pause.circle")
    let playImage = UIImage(systemName: "play.circle")
    var isPaused = true
    var roundCount = 0
    //25 min are 1500 seconds
    var secondsTotal = 60
    var totalTime = 60
    var isBreak = false
    var bgTask : UIBackgroundTaskIdentifier = UIBackgroundTaskIdentifier.invalid
    
    let getDateString = getDate()
    let createNotifications = configNotifications()
    
    var timer : Timer = Timer()
    
    //Function for the view Load
    override func viewDidLoad() {
        super.viewDidLoad()
        progressTimeBar.progress = 1
        getCurrentDate.text = getDateString.getDateStringFormat()
        print(getDateString.getCurrentTime())
        statePhone()
        
    }

    //FUnction for the start and pause the timer
    @IBAction func startOrPauseTime(_ sender: Any) {
        if isPaused {
            timer = Timer.scheduledTimer(timeInterval: 1.0,
                                         target: self,
                                         selector: #selector(getTimerRunning),
                                         userInfo: nil, repeats: true)
            
            
            RunLoop.current.add(timer, forMode: RunLoop.Mode.default)
            
            buttonStartOrPause.setImage(pauseImage, for: .normal)
            
            isPaused = false
            
        }else{
            timer.invalidate()
            buttonStartOrPause.setImage(playImage, for: .normal)
            isPaused = true
        }
    }
    
    //Function to do the process of countdown the timer for each second
    //make the progress of the bar, to make it more clear the time is running
    //If the seocnd reach 0 is necessary to start form the beginign the
    //progress bar and change the labels with the correct string
    @objc func getTimerRunning(){
        
        if secondsTotal == 65 || secondsTotal == 90{
            totalTime = secondsTotal
            isBreak = true
        }else if secondsTotal == 60 {
            totalTime = secondsTotal
        }
        
        secondsTotal -= 1
        
        progressTimeBar.setProgress((Float(secondsTotal)/Float(totalTime)), animated: true)
        
        let minutes = Int(secondsTotal / 60 % 60)
        let seconds = Int(secondsTotal % 60)
        
        if (minutes < 10){
            getTimerUpdate.text = "0\(minutes):\(seconds)"
        }
        if (minutes < 10 && seconds < 10) {
            getTimerUpdate.text = "0\(minutes):0\(seconds)"
        }
        
        if secondsTotal == 0 {
            progressTimeBar.progress = 1
            createNotifications.sendNotification(isBreak: isBreak)
            addRoundAndChangeTime()
            UIApplication.shared.endBackgroundTask(bgTask)
        }
    }
    
    func addRoundAndChangeTime(){
        if isBreak {
            secondsTotal = 60
            getTimerUpdate.text = "01:00"
            isBreak = false
        }else{
            roundCount += 1
            if roundCount == 4 {
                secondsTotal = 90
                getTimerUpdate.text = "01:30"
                roundCount = 0
                totalRoundsCount.text = "\(roundCount)/4"
            }else{
                totalRoundsCount.text = "\(roundCount)/4"
                secondsTotal = 65
                getTimerUpdate.text = "01:05"
            }
        }
        
        isPaused = true
        buttonStartOrPause.setImage(playImage, for: .normal)
        timer.invalidate()
    }
    
    func statePhone(){
        let state = UIApplication.shared.applicationState
        if state == .active {
           print("I'm active")
        }
        else if state == .inactive {
           print("I'm inactive")
        }
        else if state == .background {
           print("I'm in background")
           bgTask = UIApplication.shared.beginBackgroundTask(expirationHandler: { UIApplication.shared.endBackgroundTask(self.bgTask) })
        }
    }
    
}


IOS will kill the background task after a set amount of time (between 3 and 10 minutes, not sure exactly), so this won’t work. There are only certain things iOS will keep running (VOIP and audio).

1 Like

Thanks Dan!, so I guess I will need to look something else I’m wondering how apple did his timer or other apps were created

I am puzzled by this as well. I think solution is that you check against the system clock. But this doesn’t explain how to inform the user. Must be able to schedule push notifications, so you would be able to say “at {n} o’clock, send a notification”, which, in combo with checking the app against clock time when reopened would work.

Maybe is a little bit late but if anyone is having the same question. The solution for this, thanks for the idea Dan, was to compare two dates, one will be when you start your timer and the other one when is come back to be active or foreground. You only need to use the TimerIntervalSince() function, if you want to know the difference in seconds and update the total seconds in your timer

1 Like