我的应用程序的核心部分是一个计时器。到目前为止,这是代码(从主代码库中取出,以尽量减少可重现性):
import SwiftUI
struct TestView: View {
@State var countdownTimer = 5 * 60 /// The actual seconds of the timer, being counted down/up
@State var timerRunning = false /// Var to set and see if timer is running
var body: some View {
/// For formatting the timer numbers
let style = Duration.TimeFormatStyle(pattern: .minuteSecond)
let formTimer = Duration.seconds(countdownTimer).formatted()
let formTimerShort = Duration.seconds(countdownTimer).formatted(style)
/// The main Timer If there are no hours we hide the hour number and make the timer bigger in this way.
Text(countdownTimer / 3600 == 0 && timerRunning ? "\(formTimerShort)" : "\(formTimer)")
.onReceive(Timer.publish(every: 1, on: .main, in: .common).autoconnect()) { _ in
if countdownTimer > 0 && timerRunning {
countdownTimer -= 1
} else {
timerRunning = false
}
}
/// The Start and Pause buttons
HStack {
if !timerRunning {
Button(action: {
timerRunning = true
}) {
Text("Start")
.frame(width: 300, height: 75)
.font(.system(size: 60))
}
} else {
Button(action: {
timerRunning = false
}) {
Text("Pause")
.frame(width: 300, height: 75)
.font(.system(size: 60))
}
}
}
}
}
#Preview {
TestView()
}
问题在于它不准确。1 小时的计时器使用时间大约为 40 秒。所以我的想法是将计时器的发布时间从 1 改为 0.001,从而提高分辨率。此代码有效,但准确性更低!
struct TestView: View {
@State var countdownTimer = 5 * 60 /// The actual seconds of the timer, being counted down/up
@State private var fractionTimer = 0 /// Helping us to make the timer more accurate
@State var timerRunning = false /// Var to set and see if timer is running
var body: some View {
[...]
/// The main Timer If there are no hours we hide the hour number and make the timer bigger in this way.
Text(countdownTimer / 3600 == 0 && timerRunning ? "\(formTimerShort)" : "\(formTimer)")
.onReceive(Timer.publish(every: 0.001, on: .main, in: .common).autoconnect()) { _ in
/// Count up the fraction timer, only if he hits a certain Number we count up our Visible Timer
if timerRunning {
fractionTimer += 1
}
if fractionTimer == 1000 {
if countdownTimer > 0 && timerRunning {
countdownTimer -= 1
fractionTimer = 0
} else {
timerRunning = false
fractionTimer = 0
}
}
}
[...]
}
}
根据我的逻辑,这应该将误差从每小时 40 秒降低到每小时 0.04 秒。但实际上现在误差是每小时 120 秒!
怎么会这样?是不是因为它在主线程上运行,并且每秒调用 1000 次,所以有许多其他任务会延迟该调用?我应该从主线程中获取它吗?
我还没有在 SwiftUI 中使用过太多计时器,但一般来说,您不想依赖计时器来提供您要求的确切调用次数。(如果系统在计时器应该触发的时刻很忙,它们偶尔会“跳过一个节拍”。)您应该使用
Timer
来更新您的 UI,然后使用数学运算与当前对象Date
和结束对象来计算已经过去的时间量。(因此,回到每秒触发一次或每秒触发几次的计时器,但每次计时器触发时都Date
根据当前时间计算剩余的秒数。)Date
考虑一下您的代码的这种变化,它不会浪费几秒钟:
不准确的原因可能是每次触发时计时器都会丢失,而滥用 onChange 会使问题更加严重。为了防止丢失,计时器发布者需要处于状态,例如
onChange 用于外部操作,而不是用于更新状态。在调用 body 之前,状态应该已经具有正确的值。
此外,主体内的 if 会破坏 SwiftUI 的身份,导致额外的堆分配并降低其速度。您应该有一个按钮声明并在其参数中使用 if。