update label from background timer

前端 未结 1 1560
北荒
北荒 2020-11-28 16:23

Good Day,

I am developing a workout app that works fine except when moving it to the background. The timer suspends when it does. I found an example of a backgroun

相关标签:
1条回答
  • 2020-11-28 16:41

    Instead of trying to run a timer in the background, record the startDate of the start of your workout and compute the time interval. That way, the app doesn't actually have to run in the background to keep track of the workout time. The timer will only be used to update the user interface.

    Pausing now works by recording the current workout interval. When the workout restarts, it subtracts the current workout interval from the Date() to get a new adjusted startDate.

    Add notifications for the app entering the background and foreground so that you can restart the UI update timer if the workout is active:

    import UIKit
    
    enum WorkoutState {
        case inactive
        case active
        case paused
    }
    
    class ViewController: UIViewController {
    
        var workoutState = WorkoutState.inactive
        var workoutInterval = 0.0
        var startDate = Date()
    
        var timer = Timer()
    
        @IBOutlet weak var outputLabel: UILabel!
    
        @IBOutlet weak var start: UIButton!
    
        @IBOutlet weak var paused: UIButton!
    
        @IBAction func startButton(_ sender: UIButton) {
    
            startButtonPressed()
    
        }
    
        @IBAction func pausedButton(_ sender: UIButton) {
    
            pausedButtonPressed()
    
        }
    
        @IBOutlet weak var timerLabel: UILabel!
    
        func updateTimerLabel() {
            let interval = -Int(startDate.timeIntervalSinceNow)
            let hours = interval / 3600
            let minutes = interval / 60 % 60
            let seconds = interval % 60
    
            timerLabel.text = String(format:"%02i:%02i:%02i", hours, minutes, seconds)
    
        }
    
        func startButtonPressed() {
    
            if workoutState == .inactive {
                startDate = Date()
            } else if workoutState == .paused {
                startDate = Date().addingTimeInterval(-workoutInterval)
            }
            workoutState = .active
    
            outputLabel.text = "Workout Started"
            start.isHidden = true
            paused.isHidden = false
    
            updateTimerLabel()
            _foregroundTimer(repeated: true)
            print("Calling _foregroundTimer(_:)")
    
        }
    
        func pausedButtonPressed(){
    
            // record workout duration
            workoutInterval = floor(-startDate.timeIntervalSinceNow)
    
            outputLabel.text = "Workout Paused"
            workoutState = .paused
            timer.invalidate()
            pauseWorkout()
    
        }
    
        func pauseWorkout(){
    
            paused.isHidden = true
            start.isHidden = false
    
        }
    
        func _foregroundTimer(repeated: Bool) -> Void {
            NSLog("_foregroundTimer invoked.");
    
            //Define a Timer
            self.timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(self.timerAction(_:)), userInfo: nil, repeats: true);
            print("Starting timer")
    
        }
    
        @objc func timerAction(_ timer: Timer) {
    
            print("timerAction(_:)")
    
            self.updateTimerLabel()
        }
    
        @objc func observerMethod(notification: NSNotification) {
    
            if notification.name == .UIApplicationDidEnterBackground {
                print("app entering background")
    
                // stop UI update
                timer.invalidate()
            } else if notification.name == .UIApplicationDidBecomeActive {
                print("app entering foreground")
    
                if workoutState == .active {
                    updateTimerLabel()
                    _foregroundTimer(repeated: true)
                }
            }
    
        }
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            NotificationCenter.default.addObserver(self, selector: #selector(observerMethod), name: .UIApplicationDidEnterBackground, object: nil)
    
            NotificationCenter.default.addObserver(self, selector: #selector(observerMethod), name: .UIApplicationDidBecomeActive, object: nil)
    
            print("viewDidLoad()")
    
            print("Hiding buttons")
            paused.isHidden = true
            start.isHidden = false
    
            print("Clearing Labels")
            outputLabel.text = ""
            timerLabel.text = ""
    
            print("\(timer)")
            timer.invalidate()
        }
    }
    

    Original Answer

    Just call updateTimerLabel() on the main loop:

    DispatchQueue.main.async {
        self.updateTimerLabel()
    }
    

    Full function:

    @objc func _backgroundTimerAction(_ timer: Timer) {
    
        print("_backgroundTimerAction(_:)")
    
        time += 1
    
        DispatchQueue.main.async {
            self.updateTimerLabel()
        }
    
        NSLog("time count -> \(time)")
    }
    

    Notes:

    1. Running the timer on a background thread isn't buying you anything but trouble in setting it up. I'd recommend just running it on the main thread.
    2. There is no need to add -> Void to a Swift function definition; that is the default.
    3. Swift typically doesn't need the semicolons ;, so lose those.
    4. self.time is already an Int, so creating a new Int from it is unnecessary.

      replace:

      let hours = Int(self.time) / 3600
      

      with:

      let hours = self.time / 3600
      
    0 讨论(0)
提交回复
热议问题