'Create a Timer Publisher using Swift Combine
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
}
}
}
Solution 1:[1]
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
}
}
}
Solution 2:[2]
Using 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)")
}
}
Solution 3:[3]
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
Solution 4:[4]
a timer that runs from 0 to 9.
struct PlayerView : View {
@State private var currentTime: TimeInterval = 0.0
@ObservedObject var player = PodcastPlayer()
var body: some View {
Text("\(Int(currentTime))")
.font(.largeTitle)
.onReceive(player.$currentTimePublisher.filter { $0 < 10.0 }) { newCurrentTime in
self.currentTime = newCurrentTime
}
}
}
class PodcastPlayer: ObservableObject {
@Published var currentTimePublisher: TimeInterval = 0.0
init() {
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
self.currentTimePublisher += 1
}
}
}
Solution 5:[5]
just my 2 cents in a more general code from excellent sample from "kontiki"
struct ContentView : View {
@State private var currentTime: TimeInterval = 0.0
@ObservedObject var counter = Counter(?t: 1)
var body: some View {
VStack{
Text("\(Int(currentTime))")
.font(.largeTitle)
.onReceive(counter.$currentTimePublisher) { newCurrentTime in
self.currentTime = newCurrentTime
}
}
Spacer().frame(height: 30)
Button (action: { counter.reset() } ){
Text("reset")
}
Spacer().frame(height: 30)
Button (action: { counter.kill() } ){
Text("KILL")
}
Spacer().frame(height: 30)
Button (action: { counter.restart(?t: 0.1) } ){
Text("Restart")
}
}
}
class Counter: ObservableObject {
@Published var currentTimePublisher: TimeInterval = 0.0
private var timer: Timer?
init(?t: Double) {
self.timer = Timer.scheduledTimer(withTimeInterval: ?t, repeats: true) { _ in
self.currentTimePublisher += 1
}
}
func restart(?t: Double){
kill()
self.timer = Timer.scheduledTimer(withTimeInterval: ?t, repeats: true) { _ in
self.currentTimePublisher += 1
}
}
func kill(){
self.timer?.invalidate()
self.timer = nil
}
func reset(){
currentTimePublisher = 0
}
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | kontiki |
Solution 2 | malhal |
Solution 3 | cayZ |
Solution 4 | David Buck |
Solution 5 | ingconti |