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

iqih9akk  于 2023-02-26  发布在  iOS
关注(0)|答案(2)|浏览(280)

我已经构建了一个基本的计时器应用程序,我正在尝试弄清楚如何在后台运行计时器。我已经尝试了签名和功能中的后台模式,但似乎不适合我。
我目前正在开发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()
                }
            }
        })
    }
}

如果你们中的任何人有任何关于更好地管理计时器的建议,请一定让我知道。谢谢。

hfwmuf9z

hfwmuf9z1#

当用户离开应用程序时,它会被挂起。当用户离开应用程序时,通常不会让计时器继续运行。我们不想在用户返回应用程序之前,为了更新一个不相关的计时器而耗尽用户的电池。
这显然意味着您不想使用“计数器”模式,而是在启动计时器时捕获Date,并保存它,以防用户离开应用:

func saveStartTime() {
    if let startTime = startTime {
        UserDefaults.standard.set(startTime, forKey: "startTime")
    } else {
        UserDefaults.standard.removeObject(forKey: "startTime")
    }
}

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

func fetchStartTime() -> Date? {
    UserDefaults.standard.object(forKey: "startTime") as? Date
}

计时器现在不应该使用计数器,而是计算从开始时间到现在的时间:

let now = Date()
let elapsed = now.timeIntervalSince(startTime)

guard elapsed < 15 else {
    self.stop()
    return
}

self.message = String(format: "%0.1f", elapsed)

就我个人而言,我会从View中抽象出计时器和持久性:

class Stopwatch: ObservableObject {
    /// String to show in UI
    @Published private(set) var message = "Not running"

    /// Is the timer running?
    @Published private(set) var isRunning = false

    /// Time that we're counting from
    private var startTime: Date?                        { didSet { saveStartTime() } }

    /// The timer
    private var timer: AnyCancellable?

    init() {
        startTime = fetchStartTime()

        if startTime != nil {
            start()
        }
    }
}

// MARK: - Public Interface

extension Stopwatch {
    func start() {
        timer?.cancel()               // cancel timer if any

        if startTime == nil {
            startTime = Date()
        }

        message = ""

        timer = Timer
            .publish(every: 0.1, on: .main, in: .common)
            .autoconnect()
            .sink { [weak self] _ in
                guard
                    let self = self,
                    let startTime = self.startTime
                else { return }

                let now = Date()
                let elapsed = now.timeIntervalSince(startTime)

                guard elapsed < 60 else {
                    self.stop()
                    return
                }

                self.message = String(format: "%0.1f", elapsed)
            }

        isRunning = true
    }

    func stop() {
        timer?.cancel()
        timer = nil
        startTime = nil
        isRunning = false
        message = "Not running"
    }
}

// MARK: - Private implementation

private extension Stopwatch {
    func saveStartTime() {
        if let startTime = startTime {
            UserDefaults.standard.set(startTime, forKey: "startTime")
        } else {
            UserDefaults.standard.removeObject(forKey: "startTime")
        }
    }

    func fetchStartTime() -> Date? {
        UserDefaults.standard.object(forKey: "startTime") as? Date
    }
}

然后视图可以只使用Stopwatch

struct ContentView: View {
    @ObservedObject var stopwatch = Stopwatch()

    var body: some View {
        VStack {
            Text(stopwatch.message)
            Button(stopwatch.isRunning ? "Stop" : "Start") {
                if stopwatch.isRunning {
                    stopwatch.stop()
                } else {
                    stopwatch.start()
                }
            }
        }
    }
}

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

7eumitmz

7eumitmz2#

import SwiftUI

struct BackgroundTimer: View {
    @State var start = false
    @State var count = 0
        
    var body: some View {
        VStack {
            Text(count.description)
            Text("Timer isRunning == \(start.description)")
            Button(action: {
                start = true
                count = 0
                Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
                    count += 1
                    
                    if count == 15 {
                        timer.invalidate()
                        start = false
                    }
                }
            }, label: {
                Text("Start Timer")
            })
        }
        
    }
}

struct BackgroundTimer_Previews: PreviewProvider {
    static var previews: some View {
        BackgroundTimer()
    }
}

相关问题