SwiftUI:如何在后台运行计时器

Pra*_*Sen 5 ios swiftui

我已经构建了一个基本的计时器应用程序,我正在尝试弄清楚如何在后台运行计时器。我已经尝试过签名和功能的后台模式,但似乎不适合我。

我目前正在开发 Xcode 12 beta 6。

代码

struct ContentView: View {
    @State var start = false
    @State var count = 0
    
    var timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
    
    var body: some View {
        ZStack {
            // App content.
        }
        .onReceive(timer, perform: { _ in
            if start {
                if count < 15 {
                    count += 1
                } else {
                    start.toggle()
                }
            }
        })
    }
}
Run Code Online (Sandbox Code Playgroud)

如果你们中的任何人对更好地管理计时器有任何建议,请告诉我。谢谢。

Rob*_*Rob 15

当用户离开应用程序时,它会被暂停。当用户离开应用程序时,通常不会\xe2\x80\x99 保持计时器继续运行。我们不想耗尽用户\xe2\x80\x99的电池来更新在用户返回应用程序之前\xe2\x80\x99真正相关的计时器。

\n

这显然意味着您不想使用 \xe2\x80\x9ccounter\xe2\x80\x9d 模式。相反,捕获Date启动计时器的时间,并保存它,以防用户离开应用程序:

\n
func saveStartTime() {\n    if let startTime = startTime {\n        UserDefaults.standard.set(startTime, forKey: "startTime")\n    } else {\n        UserDefaults.standard.removeObject(forKey: "startTime")\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

并且,当应用程序启动时,检索保存的startTime

\n
func fetchStartTime() -> Date? {\n    UserDefaults.standard.object(forKey: "startTime") as? Date\n}\n
Run Code Online (Sandbox Code Playgroud)\n

你的计时器现在不应该使用计数器,而是计算开始时间和现在之间经过的时间:

\n
let now = Date()\nlet elapsed = now.timeIntervalSince(startTime)\n\nguard elapsed < 15 else {\n    self.stop()\n    return\n}\n\nself.message = String(format: "%0.1f", elapsed)\n
Run Code Online (Sandbox Code Playgroud)\n
\n

就我个人而言,我会从以下内容中抽象出这个计时器和持久性内容View

\n
class Stopwatch: ObservableObject {\n    /// String to show in UI\n    @Published private(set) var message = "Not running"\n\n    /// Is the timer running?\n    @Published private(set) var isRunning = false\n\n    /// Time that we're counting from\n    private var startTime: Date? { didSet { saveStartTime() } }\n\n    /// The timer\n    private var timer: AnyCancellable?\n\n    init() {\n        startTime = fetchStartTime()\n\n        if startTime != nil {\n            start()\n        }\n    }\n}\n\n// MARK: - Public Interface\n\nextension Stopwatch {\n    func start() {\n        timer?.cancel() // cancel timer if any\n\n        if startTime == nil {\n            startTime = Date()\n        }\n\n        message = ""\n\n        timer = Timer\n            .publish(every: 0.1, on: .main, in: .common)\n            .autoconnect()\n            .sink { [weak self] _ in\n                guard\n                    let self = self,\n                    let startTime = self.startTime\n                else { return }\n\n                let now = Date()\n                let elapsed = now.timeIntervalSince(startTime)\n\n                guard elapsed < 60 else {\n                    self.stop()\n                    return\n                }\n\n                self.message = String(format: "%0.1f", elapsed)\n            }\n\n        isRunning = true\n    }\n\n    func stop() {\n        timer?.cancel()\n        timer = nil\n        startTime = nil\n        isRunning = false\n        message = "Not running"\n    }\n}\n\n// MARK: - Private implementation\n\nprivate extension Stopwatch {\n    func saveStartTime() {\n        if let startTime = startTime {\n            UserDefaults.standard.set(startTime, forKey: "startTime")\n        } else {\n            UserDefaults.standard.removeObject(forKey: "startTime")\n        }\n    }\n\n    func fetchStartTime() -> Date? {\n        UserDefaults.standard.object(forKey: "startTime") as? Date\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

然后视图就可以使用这个Stopwatch

\n
struct ContentView: View {\n    @ObservedObject var stopwatch = Stopwatch()\n\n    var body: some View {\n        VStack {\n            Text(stopwatch.message)\n            Button(stopwatch.isRunning ? "Stop" : "Start") {\n                if stopwatch.isRunning {\n                    stopwatch.stop()\n                } else {\n                    stopwatch.start()\n                }\n            }\n        }\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n
\n

FWIW,UserDefaults可能不是存储这个的正确位置startTime。我可能会使用 plist 或 CoreData 或其他什么。但我想让上面的内容尽可能简单,以说明持久化的想法,startTime以便当应用程序再次启动时,您可以使其看起来像计时器在后台运行,即使它是 \xe2\x80\ x99t。

\n