I've been watching the Data Flow Through SwiftUI WWDC talk. They have a slide with a sample code where they use a Timer publisher that gets connected to a SwiftUI View, and updates the UI with the time.
I'm working on some code where I want to do the exact same thing, but can't figure out how this PodcastPlayer.currentTimePublisher is implemented, and then hooked to the UI struct. I have also watched all the videos about Combine.
How can I achieve this?
The sample code:
struct PlayerView : View {
  let episode: Episode
  @State private var isPlaying: Bool = true
  @State private var currentTime: TimeInterval = 0.0
  var body: some View {
    VStack { // ...
      Text("\(playhead, formatter: currentTimeFormatter)")
    }
    .onReceive(PodcastPlayer.currentTimePublisher) { newCurrentTime in
      self.currentTime = newCurrentTime
    }
  }
}
                Here you have an example of a Combine timer. I am using a global, but of course you should use whatever is applicable to your scenario (environmentObject, State, etc).
import SwiftUI
import Combine
class MyTimer {
    let currentTimePublisher = Timer.TimerPublisher(interval: 1.0, runLoop: .main, mode: .default)
    let cancellable: AnyCancellable?
    init() {
        self.cancellable = currentTimePublisher.connect() as? AnyCancellable
    }
    deinit {
        self.cancellable?.cancel()
    }
}
let timer = MyTimer()
struct Clock : View {
  @State private var currentTime: Date = Date()
  var body: some View {
    VStack {
      Text("\(currentTime)")
    }
    .onReceive(timer.currentTimePublisher) { newCurrentTime in
      self.currentTime = newCurrentTime
    }
  }
}
                        ObservableObject
to Create a Timer Publisher using Swift Combine
class TimeCounter: ObservableObject {
    @Published var time = 0
    
    lazy var timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in self.time += 1 }
    init() { timer.fire() }
}
That's it! now you just need to observe for changes:
struct ContentView: View {
    @StateObject var timeCounter = TimeCounter()
    
    var body: some View {
        Text("\(timeCounter.time)")
    }
}
                        I implemented a Combine timer with a new feature allowing you to switch between different intervals.
class CombineTimer {
    private let intervalSubject: CurrentValueSubject<TimeInterval, Never>
    var interval: TimeInterval {
        get {
            intervalSubject.value
        }
        set {
            intervalSubject.send(newValue)
        }
    }
    var publisher: AnyPublisher<Date, Never> {
        intervalSubject
            .map {
                Timer.TimerPublisher(interval: $0, runLoop: .main, mode: .default).autoconnect()
            }
            .switchToLatest()
            .eraseToAnyPublisher()
    }
    init(interval: TimeInterval = 1.0) {
        intervalSubject = CurrentValueSubject<TimeInterval, Never>(interval)
    }
}
To start the timer, simply subscribe to the publisher property.
SomeView()
    .onReceive(combineTimer.publisher) { date in
        // ...
    }
You can switch to a new timer with a different interval by changing the interval property.
combineTimer.interval = someNewInterval
                        If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With