SwiftUI - Optional Timer, reset and recreate

左心房为你撑大大i 提交于 2021-02-08 08:21:29

问题


Normally, I would use an optional variable to hold my Timer reference, as it's nice to be able to invalidate and set it to nil before recreating.

I'm trying to use SwiftUI and want to make sure I'm correctly doing so...

I declare as:

@State var timer:Publishers.Autoconnect<Timer.TimerPublisher>? = nil

Later I:

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

To drive a UI text control I use:

.onReceive(timer) { time in
    print("The time is now \(time)")
}

What is the right way with this Combine typed Timer to invalidate and recreate?

I've read one should call:

self.timer.upstream.connect().cancel()

However, do I also need to invalidate or simply then nil out?


回答1:


There is no need to throw away the TimerPublisher itself. Timer.publish creates a Timer.TimerPublisher instance, which like all other publishers, only starts emitting values when you create a subscription to it - and it stops emitting as soon as the subscription is closed.

So instead of recreating the TimerPublisher, you just need to recreate the subscription to it - when the need arises.

So assign the Timer.publish on declaration, but don't autoconnect() it. Whenever you want to start the timer, call connect on it and save the Cancellable in an instance property. Then whenever you want to stop the timer, call cancel on the Cancellable and set it to nil.

You can find below a fully working view with a preview that starts the timer after 5 seconds, updates the view every second and stops streaming after 30 seconds.

This can be improved further by storing the publisher and the subscription on a view model and just injecting that into the view.

struct TimerView: View {
    @State private var text: String = "Not started"

    private var timerSubscription: Cancellable?

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

    var body: some View {
        Text(text)
            .onReceive(timer) {
                self.text = "The time is now \($0)"
            }
    }

    mutating func startTimer() {
        timerSubscription = timer.connect()
    }

    mutating func stopTimer() {
        timerSubscription?.cancel()
        timerSubscription = nil
    }
}

struct TimerView_Previews: PreviewProvider {
    static var previews: some View {
        var timerView = TimerView()
        DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
            timerView.startTimer()
        }
        DispatchQueue.main.asyncAfter(deadline: .now() + 30) {
            timerView.stopTimer()
        }
        return timerView
    }
}

With a view model, you don't even need to expose a TimerPublisher (or any Publisher) to the view, but can simply update an @Published property and display that in the body of your view. This enables you to declare timer as autoconnect, which means you don't manually need to call cancel on it, you can simply nil out the subscription reference to stop the timer.

class TimerViewModel: ObservableObject {
    private let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()

    private var timerSubscription: Cancellable?

    @Published var time: Date = Date()

    func startTimer() {
        timerSubscription = timer.assign(to: \.time, on: self)
    }

    func stopTimer() {
        timerSubscription = nil
    }
}

struct TimerView: View {
    @ObservedObject var viewModel: TimerViewModel

    var body: some View {
        Text(viewModel.time.description)
    }
}

struct TimerView_Previews: PreviewProvider {
    static var previews: some View {
        let viewModel = TimerViewModel()
        let timerView = TimerView(viewModel: viewModel)
        DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
            viewModel.startTimer()
        }
        DispatchQueue.main.asyncAfter(deadline: .now() + 30) {
            viewModel.stopTimer()
        }
        return timerView
    }
}


来源:https://stackoverflow.com/questions/62678977/swiftui-optional-timer-reset-and-recreate

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!