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

Ios SwiftUI:如何在后台运行计时器,ios,swiftui,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()

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

我目前正在研究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()
                }
            }
        })
    }
}
struct ContentView:View{
@State var start=false
@状态变量计数=0
var timer=timer.publish(每隔:1,在:.main上,在:.common中)。自动连接()
var body:一些观点{
ZStack{
//应用程序内容。
}
.onReceive(计时器,执行:{uu}in
如果开始{
如果计数小于15{
计数+=1
}否则{
start.toggle()
}
}
})
}
}
如果你们中有人对如何更好地管理计时器有任何建议,请务必告诉我。谢谢。

导入快捷界面

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

当用户离开应用程序时,它将被挂起。当用户离开应用程序时,通常不会让计时器继续运行。我们不想为了更新一个在用户返回应用程序之前并不相关的计时器而杀死用户的电池

这显然意味着您不想使用“计数器”模式。相反,在启动计时器时捕获
日期
,并将其保存,以防用户离开应用程序:

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)
然后视图就可以使用此秒表了:

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之类的工具。但是我想让上面的内容尽可能简单,以说明坚持
开始时间的想法,这样当应用程序再次启动时,你就可以让它看起来像是在后台运行的计时器,即使它不是。

计数器也不是构建已用时间计时器的好方法,因为
计时器
不是很准确;它可能会有明显的抖动,这取决于你的应用程序正在做什么。最好使用
Date
对象来跟踪开始时间并计算经过的时间。谢谢@Paulw11,我会确保这样做。嘿@Rob,谢谢你的详细回答。您的代码提供了信息。我将确保在视图之外实现计时器。当然,使用计时器的应用程序的理想场景是在后台暂停计时器,例如,一个测验应用程序。然而,正如我前面提到的,我正在寻找一种在后台运行计时器的解决方案。只是一个简单的定时器应用程序,类似于苹果的时钟定时器应用程序。你的“秒表”课程让我以一种不同的方式来处理事情,谢谢。“当然,带计时器的应用程序的理想场景是在后台暂停计时器”。。。这完全取决于计时器的用途。当然,在游戏中,你可能需要暂停计时器。但如果编写秒表/计时器应用程序,则您希望在离开应用程序时显示为继续。你的问题题为“如何在后台运行计时器”,所以我猜你是在谈论后者。嗨,罗布!我很乐意在你的答案上打勾。毫无疑问,你的回答很有用。然而,我最初的问题仍然没有解决。既然你已经理解了我原来的问题,你能帮我解答一下吗?我不能理解这个问题,因为我以为我已经回答了。你的问题是“如何让计时器在后台运行”,答案是“技术上你不应该这样做;但这是如何在功能上完成同样的事情。”也许你可以澄清一下,上面提到的方法无法实现你想要的。好吧!当您从iPhone中的时钟应用程序运行计时器/秒表并退出该应用程序时,计时器/秒表不会停止。这就是我想用我的应用程序实现的目标。:)我希望我能澄清我自己。顺便说一句,我同意你的问题,只是因为它提供了信息。谢谢
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()
                }
            }
        }
    }
}