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

pnwntuvh  于 2023-04-19  发布在  Swift
关注(0)|答案(4)|浏览(213)

我有一个导航视图,由主ContentView和一个TimerView组成。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()
    }
}
amrnrhlw

amrnrhlw1#

据我所知(和历史上),定时器不能在取消或无效后重新启动。
我所做的只是重新声明计时器。

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
    }
}
6fe3ivhb

6fe3ivhb2#

View中有一个没有附加到@State变量或@ObservedObject模型的变量似乎没有很好的定义文档-但一般规则是,没有@State/@ObservedObject注解的所有内容基本上都是一次性的-特别是因为TimerViewstruct,并且不会在重新渲染时保留。
Timer.TimerPublisher是一个类,因此本质上可以归结为两个用例

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

你有@State var secondsElapsed让我认为它应该是 transient 的

klr1opcd

klr1opcd3#

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

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

798qvoo8

798qvoo84#

更改发布者可能会导致状态重置,这可能是不希望的(例如取消动画)
下面是使用ObservableObject对象另一个take

class TimerUpdater: NSObject, ObservableObject {
    
    @InvalidatingTimer var timer: Timer?
    
    @Published var tick: Int = 0
    
    var repeating: TimeInterval = 1
    
    init(repeating: TimeInterval) {
     
        super.init()
        
        self.repeating = repeating
        
        continueTimer()
        
    }
    
    func stopTimer() {
        timer = nil
    }

    func continueTimer() {
        timer = nil
        timer = Timer.scheduledTimer(withTimeInterval: repeating, repeats: true, block: { [weak self] timer in
            self?.tick += 1
        })
    }

}

struct SampleViewThatNeedToBeTimerUpdated: View {
    
    @StateObject var timerUpdater = TimerUpdater(repeating: 5)
    
    var body: some View {
        
        SomeView()
        .onDraggingBegan({
            timerUpdater.stopTimer()
        })
        .onDraggingEnded({
            timerUpdater.continueTimer()
        })
        .onChange(of: timerUpdater.tick, perform: { _ in
            
            withAnimation {
                ...
            }
            
        })
        .onDisappear {
            timerUpdater.stopTimer()
        }
                  
    }
    
}

相关问题