Search code examples
xcodetimerswiftui

Swiftui timer not firing after navigating back


i have a timer in SwiftUI which works when opening the view for the first time. When navigating back and opening again, the timer does not start. Any idea what can be wrong ?

import SwiftUI

struct ClockDetail: View {

    @State var seconds: Int = 0
    @ObservedObject var motion: MotionManager
    let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()

    var body: some View {
        VStack(alignment: .leading) {
            Text("\(seconds)").font(.title).onReceive(timer) { output in
                self.seconds += 1
            }
            Text("Attitude Data").foregroundColor(.blue)
            Text("Roll: \(motion.roll)")
            Text("Pitch: \(motion.pitch)")
            Text("Yaw: \(motion.yaw)")
        }.onDisappear(perform: {
            self.timer.upstream.connect().cancel()
        })
    }
}


struct ClockDetail_Previews: PreviewProvider {
    static var previews: some View {
        ClockDetail(motion: MotionManager())
    }
}

I'm using the .onDisappear to cancel the timer but seems that doesn't do the job.


Solution

  • Your ClockDetail is created only once, so once you invalidate timer it does not work anymore when you navigate again, because view is the same, but already w/o timer.

    With the introduction of view model, as in below demo approach, it is better manageable I assume.

    Tested with Xcode 11.2 / iOS 13.2. Note, I commented dependencies on not available entities, as they are not important for considered issue.

    class ClockDetailViewModel: ObservableObject {
        @Published var seconds = 0
        private var subscriber: AnyCancellable?
    
        func setup() {
            self.seconds = 0
            self.subscriber = Timer
                .publish(every: 1, on: .main, in: .common)
                .autoconnect()
                .sink(receiveValue: { _ in
                    self.seconds += 1
                })
        }
    
        func cleanup() {
            self.subscriber = nil
        }
    }
    
    struct ClockDetail: View {
    
        @State private var seconds: Int = 0
    //    @ObservedObject var motion: MotionManager
        @ObservedObject private var vm = ClockDetailViewModel()
    
        var body: some View {
            VStack(alignment: .leading) {
                Text("\(vm.seconds)").font(.title)
                Text("Attitude Data").foregroundColor(.blue)
    //            Text("Roll: \(motion.roll)")
    //            Text("Pitch: \(motion.pitch)")
    //            Text("Yaw: \(motion.yaw)")
                .onAppear {
                    self.vm.setup()
                }
                .onDisappear {
                    self.vm.cleanup()
                }
            }
        }
    }