SwiftUI:取消定时器后如何重新启动它?

Posted

技术标签:

【中文标题】SwiftUI:取消定时器后如何重新启动它?【英文标题】:SwiftUI: How do you restart a timer after cancelling it? 【发布时间】:2019-12-09 00:57:42 【问题描述】:

我有一个由主 ContentViewTimerView 组成的导航视图。 TimerView 有一个计时器,当我调用self.timer.upstream.connect().cancel() 时,它会正确递增并正确停止。

但是,当我返回 ContentView 然后再次导航到 TimerView 时,我希望计时器再次开始计数,但这不会发生。 secondsElapsed 确实重置为 0,但计时器没有运行。

import SwiftUI

struct ContentView: View 
    var body: some View 
        NavigationView 
            NavigationLink(destination: TimerView()) 
                Text("Go to Timer View")
            
        
    


struct TimerView: View 
    @State var secondsElapsed = 0
    var timer = Timer.publish (every: 1, on: .main, in: .common).autoconnect()
    var body: some View 
        VStack 
            Text("\(self.secondsElapsed) seconds elapsed")
            Button("Stop timer",
                   action: 
                    self.timer.upstream.connect().cancel()
            )
        .onReceive(timer)  _ in
            self.secondsElapsed += 1
        
    


struct ContentView_Previews: PreviewProvider 
    static var previews: some View 
        ContentView()
    

【问题讨论】:

【参考方案1】:

据我所知(并且从历史上看),定时器在取消或失效后无法重新启动。

我所做的只是重新声明计时器。

2021 年 12 月更新:

之前的代码来自 SwiftUI 1,它有一些“奇怪之处”,尤其是在生命周期方面。因此,我将代码更新为在 SwiftUI 3 中的操作方式。

对于此代码,您必须通过import combine 声明Cancellable 类型。

这是一个添加了一些个人偏好的示例:

struct TimerView: View 
    @State var secondsElapsed = 0
    @State var timer: Timer.TimerPublisher = Timer.publish(every: 1, on: .main, in: .common)
    @State var connectedTimer: Cancellable? = nil
    
    var body: some View 
        VStack 
            Text("\(self.secondsElapsed) seconds elapsed")
            Button("Stop Timer", action: 
                self.cancelTimer()
            )
            Button("Continue Timer", action: 
                self.instantiateTimer()
            )
            Button("Restart Timer", action: 
                self.restartTimer()
            )
        .onAppear 
            self.instantiateTimer()
        .onDisappear 
            self.cancelTimer()
        .onReceive(timer)  _ in
            self.secondsElapsed += 1
        
    
    
    func instantiateTimer() 
        self.timer = Timer.publish(every: 1, on: .main, in: .common)
        self.connectedTimer = self.timer.connect()
        return
    
    
    func cancelTimer() 
        self.connectedTimer?.cancel()
        return
    
    
    func resetCounter() 
        self.secondsElapsed = 0
        return
    
    
    func restartTimer() 
        self.secondsElapsed = 0
        self.cancelTimer()
        self.instantiateTimer()
        return
    

【讨论】:

self.timer.connect() 给出Result of call to 'connect()' is unused的警告 @Patrick 我回答这个问题已经有一段时间了,但我认为定时器生命周期和实例化Timer 存在一些奇怪之处(例如 .connect() 在 SwiftUI 1 中的行为不同)。尝试更新后的答案并告诉我。【参考方案2】:

View 中有一个未附加到@State 变量或@ObservedObject 模型的变量似乎没有很好定义的文档 - 但一般规则是没有@987654324 的所有内容@ 注释本质上是一次性的 - 特别是因为 TimerViewstruct 并且不会在重新渲染时保留。

Timer.TimerPublisher 是一个类 - 所以基本上这可以归结为两个用例

如果它是瞬态的(即:当视图从屏幕上移除并重新加载时重置)然后将计时器放在@State 中(注意self 捕获的强保留 - 再次说明onReceive 的文档很薄存储它的连接) 如果它是非瞬态的(即:保留在视图加载/卸载上),那么它需要进入外部模型(或全局var)并通过init 调用TimerView 引入

你有 @State var secondsElapsed 让我觉得它应该是暂时的

【讨论】:

【参考方案3】:

如果你有一个倒数计时器,你可以这样定义它:

let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()

...并将其用于文本字段,例如:

    Text("Punkte: \(Punkte.formatnumber())")
        .onReceive(timer)  input in
                            if time < 1  gameOver()  else  self.time -= TimerToggle ? 1 : 0  
                    

所以你只需要在按钮的动作中设置切换:

Button(action:  TimerToggle.toggle() , label: 
                    Text("Pause")
                )

SwiftUI 5.2

中更简单且有效

【讨论】:

以上是关于SwiftUI:取消定时器后如何重新启动它?的主要内容,如果未能解决你的问题,请参考以下文章

如何为vertx设置一个持久的计时器,如果服务器重新启动,它不会丢失?

在 WiX 中重新启动后继续安装

如何在 SwiftUI 列表中重新加载数据?

Android 警报管理器在重新启动设备或从后台杀死应用程序后取消

应用程序重新启动时照片丢失并取消固定

如何跨 SwiftUI 应用程序跟踪所有触摸