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

标签 ios swift nstimer

我正在开发计算员工工作时间的应用程序。
所以我想运行一个在后台模式下保持事件状态的计时器,但我没有找到任何直接执行此操作的方法。

我想我可以通过以下策略来实现这一点......

  1. applicationDidEnterBackground 方法中将当前时间保存在 userDefault 中。
  2. 当应用程序再次激活时,从 userDefault 中获取保存的时间,然后在 applicationWillEnterForeground 方法中找到与当前时间的差异。然后显示计算出的时间。

现在,我想知道这样做是否正确?或者有什么好的方法吗??
谢谢!!

最佳答案

没有理由在 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大多使用秒作为时间单位,所以存储和计算时最好使用秒而不是小时。如果需要显示或与服务器通信等,您可以转换为小时。)

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

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

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

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

最后,当 View Controller 被销毁时,你应该取消计时器,以防你把它放在允许它被弹出的导航 Controller 中:

    deinit {
        displayUpdateTimer?.invalidate()
    }

}

关于ios - 如何在后台模式下运行 NStimer?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49332898/

相关文章:

ios - 如何在没有任何质量损失的情况下将图像设置到 UIImageView 中并获得图像的平均颜色

ios - 获取具有所有属性值的所有 UIView subview ,iOS

ios - iOS/Cocos2d 中的哪个音频 API 最适合复杂的混音?

ios - iOS 上的 MobileVLCKit 问题(夜间构建)

ios - 切换 View Controller 后 NSTimer 触发两次

javascript - 通过javascript从iphone读取gsm号码

ios - 数据类型为 double 的无后端列将导致 nil 值

ios - Model View Presenter 和 iOS (Swift) 架构

ios - 是否应该始终将 NSTimer 添加到运行循环中以执行

ios - 如何启动单个计时器并从多个 View 访问它?