我已经建立了一个基本的计时器应用程序,并且试图弄清楚如何在后台运行计时器。
我已经从签名和功能中尝试了背景模式,但似乎不适合我。
我目前正在研究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()
                }
            }
        })
    }
}
如果您有任何建议以更好的方式管理计时器,请告诉我。谢谢。

最佳答案

当用户离开该应用程序时,该应用程序将被暂停。通常,当用户离开应用程序时,计时器不会持续计时。在用户返回应用之前,我们不想浪费用户的电池来更新与计时器无关的计时器。
显然,这意味着您不想使用“计数器”模式。而是在启动计时器时捕获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的想法,这样,当应用再次启动时,您可以使它看起来像是计时器在后台运行,即使不是。

关于ios - SwiftUI:如何在后台运行计时器,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/63765532/

10-14 21:30