SwiftUI:如何在取消计时器后重新启动计时器?

时间:2019-12-09 00:57:42

标签: ios swift swiftui

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

但是,当我返回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()
    }
}

4 个答案:

答案 0 :(得分:12)

据我所知(以及历史上的经验),取消或无效后无法重新启动Timer。

我要做的就是重新声明计时器。

以下是添加了一些个人喜好的示例:

struct TimerView: View {
    @State var secondsElapsed = 0
    @State var timer: Timer.TimerPublisher = Timer.publish (every: 1, on: .main, in: .common)
    var body: some View {
        VStack {
            Text("\(self.secondsElapsed) seconds elapsed")
            Button("Stop timer",
                   action: {
                    self.cancelTimer() // Just to be a little more DRY/maintenance-friendly, but personal preference.
            })
        }.onAppear(perform: {
            self.instantiateTimer() // You could also consider an optional self.timer variable.
            self.timer.connect() // This allows you to manually connect where you want with greater efficiency, if you don't always want to autostart.
        }).onDisappear(perform: {
            // I don't know your exact flow, but you said you want the timer to restart upon return navigation.  
            // So, I am just assuming you want to stop the timer when you navigate out.  
            // But, you can also easily remove this closure.
            // Also, this may not run as you would intuit.
            self.cancelTimer()
        }).onReceive(timer) { _ in
            self.secondsElapsed += 1
        }
    }

    func instantiateTimer() {
        self.timer = Timer.publish (every: 1, on: .main, in: .common)
        return
    }

    func cancelTimer() {
        self.timer.connect().cancel()
        return
    }
}

答案 1 :(得分:1)

View内没有附加到@State变量或@ObservedObject模型中的变量似乎没有定义明确的文档-但一般规则是所有没有@State/@ObservedObject批注的东西都是丢弃的-尤其是TimerViewstruct,并且不会在重新渲染时保留。

Timer.TimerPublisher是一个类-因此从本质上讲,这可以归结为两个用例

  • 如果它是瞬态的(即:当从屏幕上移开视图并重新加载视图时重置),则将计时器置于@State中(请注意self捕获具有很强的保留性-再次缺少文档onReceive如何存储其连接)
  • 如果它是非瞬态的(即,在视图加载/卸载时保留),则需要进入外部模型(或全局var)并通过init调用将其引入TimerView

您拥有@State var secondsElapsed使我认为它应该是短暂的

答案 2 :(得分:1)

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

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

答案 3 :(得分:-2)

尝试在viewDidAppear()中调用计时器。

如果您不熟悉viewDidAppear(),请检查此link

只要您在视图控制器上来回移动,该功能将始终被触发。