Swift 3 - How to keep the timer counting even in the background?

I want to keep my timer running even though I am in the background when I press the HOME button. How can i do this?

Here is my working code, this is a timer for training. I use an alarm when the count is complete:

import UIKit
import AVFoundation
class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {

var pickerInfo: [String] = []
var tempsCuisson:Int = 0
var timer:Timer = Timer()
var lecteur:AVAudioPlayer = AVAudioPlayer()
var estActif:Bool = false
var selection:Int?

//outlets
@IBOutlet weak var minuteurLabel: UILabel!

override func viewDidAppear(_ animated: Bool) {
    minuteurLabel.text = minuteurString(temps: tempsCuisson)
}
@IBOutlet weak var activerMinuteurBtn: UIButton!



@IBOutlet weak var pickerView: UIPickerView!
@IBOutlet weak var navBar: UINavigationBar!


//actions
@IBAction func activerMinuteurAction(_ sender: UIButton) {

    compteur()
}

@IBAction func resetMinuteurAction(_ sender: UIButton) {
    resetCompteur()
}



override func viewDidLoad() {
    super.viewDidLoad()

    //datasource + delegate
    pickerView.dataSource = self
    pickerView.delegate = self

    pickerInfo = ["00.15", "00.30", "00:45",
                  "01:00", "01:15", "01:30", "01:45",
                  "02:00", "02:15", "02:30", "02:45",
                  "03:00", "03:15", "03:30", "03:45",
                  "04:00", "04:15", "04:30", "04:45",
                  "05:00", "05:15", "05:30", "05:45",
                  "06:00", "06:15", "06:30", "06:45",
                  "07:00", "07:15", "07:30", "07:45",
                  "08:00", "08:15", "08:30", "08:45",
                  "09:00", "09:15", "09:30", "09:45", "10:00"
    ]

    activerMinuteurBtn.setTitleColor(UIColor.white, for: UIControlState.normal)

    activerMinuteurBtn.isEnabled = false
    activerMinuteurBtn.alpha = 0.3

    alarm()
}



func selectionCuisson(selection: Int) {

    var titreVC:String?

    switch selection {
    case 0:
        //code
        tempsCuisson = 015
        minuteurLabel.text = minuteurString(temps: tempsCuisson)
        navBar.topItem?.title = titre(str: pickerInfo[selection])
        break

    case 1:
        //code
        tempsCuisson = 030
        minuteurLabel.text = minuteurString(temps: tempsCuisson)
        navBar.topItem?.title  = titre(str: pickerInfo[selection])
        break


    case 2:
        //code
        tempsCuisson = 045
        minuteurLabel.text = minuteurString(temps: tempsCuisson)
        navBar.topItem?.title  = titre(str: pickerInfo[selection])
        break

    case 3:
        //code
        tempsCuisson = 060
        minuteurLabel.text = minuteurString(temps: tempsCuisson)
        navBar.topItem?.title  = titre(str: pickerInfo[selection])
        break

      

all cases ...

    default:
        //code
        print("Aucune sélection")
        break

    }

    //pour afficher option sélectionnée dans barre navigation
    //self.title = titreVC

    activerMinuteurBtn.isEnabled = true
    activerMinuteurBtn.alpha = 1
    minuteurLabel.textColor = UIColor.black

}

func minuteurString(temps: Int) -> String {
    let minutes = Int(temps) / 60 % 60
    let secondes = Int(temps) % 60

    return String(format: "%02i:%02i", minutes, secondes)
}

func compteur() {

    if (!estActif) {

        timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(ViewController.incrementer), userInfo: nil, repeats: true)
        timer.fire()
        activerMinuteurBtn.setTitle("STOP", for: UIControlState.normal)
        activerMinuteurBtn.setTitleColor(UIColor.orange, for: UIControlState.normal)
        estActif = true

    } else {

        timer.invalidate()
        activerMinuteurBtn.setTitle("Démarrer", for: UIControlState.normal)
        activerMinuteurBtn.setTitleColor(UIColor.blue, for: UIControlState.normal)
        estActif = false

    }


}

func incrementer() {

    if (tempsCuisson == 0) {

        timer.invalidate()
        minuteurLabel.text = "00:00"
        activerMinuteurBtn.setTitle("Démarrer", for: UIControlState.normal)
        activerMinuteurBtn.setTitleColor(UIColor.blue, for: UIControlState.normal)

        minuteurLabel.textColor = UIColor.green

        activerMinuteurBtn.isEnabled = false
        activerMinuteurBtn.alpha = 0.3

        lecteur.play()

    } else {
        tempsCuisson -= 1
        minuteurLabel.text = minuteurString(temps: tempsCuisson)
    }

}

func resetCompteur() {
    timer.invalidate()
    tempsCuisson = 0
    minuteurLabel.text = "00:00"
    activerMinuteurBtn.setTitle("Démarrer", for: UIControlState.normal)
    activerMinuteurBtn.setTitleColor(UIColor.white, for: UIControlState.normal)

    estActif = false

    activerMinuteurBtn.isEnabled = false
    activerMinuteurBtn.alpha = 0.3

    pickerView.selectRow(0, inComponent: 0, animated: true)

}

//AVAudioPlayer

func alarm() {
   DispatchQueue.global(qos: .userInitiated).async {
    let fichier = Bundle.main.path(forResource: "alarm", ofType: "mp3")

    do {
        try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryAmbient, with:[.duckOthers])
        try AVAudioSession.sharedInstance().setActive(true)
        try self.lecteur = AVAudioPlayer(contentsOf: (URL(string: fichier!))!)
    } catch {
        print("erreur lecture ficher MP3")
        }
    }
}

//Retourner Titre

func titre(str:String) -> String {
    return str
}

//MARK - PickerViewDataSource

// returns the number of 'columns' to display.
func numberOfComponents(in pickerView: UIPickerView) -> Int {
    return 1
}


// returns the # of rows in each component..
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
    return pickerInfo.count
}

func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {

    return pickerInfo[row]

}

//changer couleur pickerView label
func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {
    let label = UILabel(frame: CGRect(x: 0, y: 0, width: pickerView.frame.size.width, height: 44))
    label.textColor = UIColor.white
    label.font = UIFont(name: "HelveticaNeue-Bold", size: 22)
    label.textAlignment = .center
    label.text = String(format:" %@", pickerInfo[row])

    if (row == selection) {
        label.textColor = UIColor.yellow
    }

    return label
}
//MARK - PickerViewDelegate
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {

    selectionCuisson(selection: row)
    selection = row
}
}

      

+3


source to share


1 answer


Ok, so without going deep into your code, it seems like you are using Timer.scheduledTimer

to manually decrease the user-specified time every second.

This is not a good technique, as you learned - it only works when you know you have absolute control over the timing of your application.

Instead, you should save the time the user triggers the alarm, the projection time, and the timer starts to just refresh the user interface periodically.

(My code isn't perfect here, but it should point you in the right direction for solving the problem of keeping the timer running in the background.)

ex.



class ViewController: UIViewController {
    // this is pseudo-code as I don't have my compiler open :(
    let start: Date!
    let end: Date!

    func selectionCuisson(selection: Int) {

         ...

         start = Date()
         end = Date(timeInterval: tempsCuisson, since: start)
    }

 }

      

Then you create a timer that will simply update the UI.

// You can set this to be faster than the increment, for a smoother UI experience
// put in compteur()? I think
timer = Timer.scheduledTimer(timeInterval: 0.2, target: self, selector: #selector(ViewController.incrementer), userInfo: nil, repeats: true)
timer.fire()

...

func incrementer() {
    let tempsCuisson = end - start
    if tempsCuisson < 0 {
        // End your Timing Function here
        timer.invalidate()
        ...
        lecteur.play()
    } else {
        minuteurLabel.text = minuteurString(temps: tempsCuisson)
    }
}

      

You can also turn off local notification when app goes into background using date end

// when the app becomes inactive
let notification = UILocalNotification()
...
notification.fireDate = end
UIApplication.shared.scheduleLocalNotification(notification)

      

+2


source







All Articles