从后台计时器

时间:2018-05-06 13:56:20

标签: ios swift background uilabel

美好的一天,

我正在开发一种运动应用程序,除非将其移动到背景时才能正常工作。计时器暂停时。我找到了一个我工作的背景计时器的例子,但现在我无法获得显示锻炼持续时间的UILabel。在控制台中它声明我正在从我理解的主线程中访问一个对象。我不知道该怎么做是让UILabel更新,因为定时器在后台线程中更新,更新标签位于主线程中。

这就是我所拥有的(打印语句帮助我遵循代码):

import UIKit

class ViewController: UIViewController {

    var time = 0

    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 hours = Int(self.time) / 3600
        let minutes = Int(self.time) / 60 % 60
        let seconds = Int(self.time) % 60

        timerLabel.text = String(format:"%02i:%02i:%02i", hours, minutes, seconds)

    }

    func startButtonPressed() {

        outputLabel.text = "Workout Started"
        start.isHidden = true
        paused.isHidden = false

        _backgroundTimer(repeated: true)
        print("Calling _backgroundTimer(_:)")

    }

    func pausedButtonPressed(){

        outputLabel.text = "Workout Paused"
        timer.invalidate()
        pauseWorkout()

    }

    func pauseWorkout(){

        paused.isHidden = true
        start.isHidden = false

    }


    func _backgroundTimer(repeated: Bool) -> Void {
        NSLog("_backgroundTimer invoked.");

        //The thread I used is a background thread, dispatch_async will set up a background thread to execute the code in the block.

        DispatchQueue.global(qos:.userInitiated).async{
            NSLog("NSTimer will be scheduled...");

            //Define a NSTimer
            self.timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(self._backgroundTimerAction(_:)), userInfo: nil, repeats: true);
            print("Starting timer")

            //Get the current RunLoop
            let runLoop:RunLoop = RunLoop.current;

            //Add the timer to the RunLoop
            runLoop.add(self.timer, forMode: RunLoopMode.defaultRunLoopMode);

            //Invoke the run method of RunLoop manually
            NSLog("NSTimer scheduled...");
            runLoop.run();

        }

    }

    @objc func _backgroundTimerAction(_ timer: Foundation.Timer) -> Void {

        print("_backgroundTimerAction(_:)")

        time += 1

        NSLog("time count -> \(time)");
    }


    override func viewDidLoad() {
        super.viewDidLoad()

        print("viewDidLoad()")

        print("Hiding buttons")
        paused.isHidden = true
        start.isHidden = false

        print("Clearing Labels")
        outputLabel.text = ""
        timerLabel.text = ""

        print("\(timer)")
        timer.invalidate()
        time = 0

    }
}

以下是视图控制器的快照,我想更新持续时间。

snapshot

非常感谢任何人提供的任何帮助。

此致

凯文

1 个答案:

答案 0 :(得分:0)

不要尝试在后台运行计时器,而是记录锻炼开始的startDate并计算时间间隔。这样,应用程序实际上不必在后台运行以跟踪锻炼时间。计时器仅用于更新用户界面。

暂停现在可以记录当前的锻炼间隔。当锻炼重新开始时,它会从Date()中减去当前的锻炼间隔,以获得新的调整startDate

为进入后台和前台的应用添加通知,以便在锻炼处于活动状态时重新启动UI更新计时器:

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

原始答案

只需在主循环上调用updateTimerLabel()

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

全功能:

@objc func _backgroundTimerAction(_ timer: Timer) {

    print("_backgroundTimerAction(_:)")

    time += 1

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

    NSLog("time count -> \(time)")
}

注意:

  1. 在后台线程上运行计时器并不能为您提供任何设置,但在设置时却遇到了麻烦。我建议只在主线程上运行它。
  2. 无需将-> Void添加到Swift函数定义中;这是默认值。
  3. Swift通常不需要分号;,所以丢失它们。
  4. self.time已经是Int,因此无需从中创建新的Int

    取代:

    let hours = Int(self.time) / 3600
    

    使用:

    let hours = self.time / 3600