如何在后台模式下运行NStimer?

时间:2018-03-17 05:44:13

标签: ios swift nstimer

我正在开发能够计算员工工作时间的应用程序 所以我想运行一个在backGround模式下保持活着的计时器,但我没有找到任何方法如何直接这样做。

我想我可以通过遵循策略实现这一目标......

  1. 使用userDefault方法{/ 1}}保存当前时间。
  2. 当应用程序再次变为活动状态时,从applicationDidEnterBackground获取保存时间,然后在userDefault方法中找到与当前时间的差异。然后显示计算出的时间。
  3. 现在,我想知道,这是正确的方法吗?还是有什么最好的方法? 谢谢!

1 个答案:

答案 0 :(得分:2)

没有理由将当前时间保存在applicationDidEnterBackground

您在任何时候真正需要的是用户“开始工作”的时间,或者nil如果用户尚未开始工作。每当您需要知道工作时间时,您可以将其计算为当前时间减去开始工作时间:

let workStartTimeKey = "workStartTime"

class ViewController: UIViewController {

    var secondsWorked: CFTimeInterval? {
        guard UserDefaults.standard.object(forKey: workStartTimeKey) != nil else { return nil }
        let startTime = UserDefaults.standard.double(forKey: workStartTimeKey)
        return CFAbsoluteTimeGetCurrent() - startTime
    }

(由于iOS SDK主要使用第二个作为时间单位,因此最好使用秒而不是几小时来存储和计算。如果需要显示或与服务器或其他任何内容通信时,可以转换为小时数。)< / p>

我假设您的用户界面中有一个按钮供用户在开始工作时点按。点击按钮时,您可以记录当前时间,启动计时器以定期更新显示,并立即更新显示:

    @IBOutlet var startWorkButton: UIButton!

    @IBAction func startWork() {
        guard secondsWorked == nil else {
            // Already started work.
            return
        }

        UserDefaults.standard.set(CFAbsoluteTimeGetCurrent(), forKey: workStartTimeKey)
        startDisplayUpdateTimer()
        updateViews()
    }

    private var displayUpdateTimer: Timer?

    private func startDisplayUpdateTimer() {
        displayUpdateTimer = Timer(timeInterval: 1, repeats: true, block: { [weak self] _ in
            self?.updateViews()
        })
        RunLoop.main.add(displayUpdateTimer!, forMode: .commonModes)
    }

我假设你有另一个停止工作的按钮。点击它时,您获取当前值secondsWorked,清除存储的开始工作时间,取消显示更新计时器,并使用抓取的值secondsWorked执行任何操作。在这里,我只打印它:

    @IBOutlet var stopWorkButton: UIButton!

    @IBAction func stopWork() {
        guard let secondsWorked = self.secondsWorked else {
            // Didn't start work yet!
            return
        }

        displayUpdateTimer?.invalidate()
        displayUpdateTimer = nil
        UserDefaults.standard.removeObject(forKey: workStartTimeKey)
        updateViews()
        print("seconds worked: \(secondsWorked)")
    }

要更新显示,您可能希望显示标签中当前工作的时间。您还可以根据用户当前是否正在使用来启用或禁用按钮:

    @IBOutlet var timeWorkedLabel: UILabel!

    private func updateViews() {
        if let secondsWorked = secondsWorked {
            startWorkButton.isEnabled = false
            stopWorkButton.isEnabled = true
            timeWorkedLabel.text = timeWorkedFormatter.string(from: secondsWorked)
        } else {
            startWorkButton.isEnabled = true
            stopWorkButton.isEnabled = false
            timeWorkedLabel.text = "Not working"
        }
    }

    private let timeWorkedFormatter: DateComponentsFormatter = {
        let formatter = DateComponentsFormatter()
        formatter.allowedUnits = [.hour, .minute, .second]
        formatter.allowsFractionalUnits = true
        formatter.collapsesLargestUnit = true
        formatter.maximumUnitCount = 2
        formatter.unitsStyle = .abbreviated
        formatter.zeroFormattingBehavior = .dropLeading
        return formatter
    }()

加载视图后,您应该看到用户是否已经在工作。如果应用程序在她开始工作后被杀死并重新启动,就会发生这种情况。如果她已经在工作,请启动显示更新计时器。您还需要更新视图,无论如何:

    override func viewDidLoad() {
        super.viewDidLoad()
        if secondsWorked != nil { startDisplayUpdateTimer() }
        updateViews()
    }

最后,您应该在视图控制器被销毁时取消计时器,以防您在允许弹出它的导航控制器中使用它:

    deinit {
        displayUpdateTimer?.invalidate()
    }

}