Skip to content

Instantly share code, notes, and snippets.

@kaunteya
Created May 28, 2021 09:18
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kaunteya/6294ab5a08e3e94aba523eafe5f8e66a to your computer and use it in GitHub Desktop.
Save kaunteya/6294ab5a08e3e94aba523eafe5f8e66a to your computer and use it in GitHub Desktop.
CountDown timer implemented in combine
import Foundation
import Combine
struct CountDownTimer: Publisher {
struct TimeRemaining: CustomStringConvertible {
let min, seconds, totalSeconds: Int
var description: String {
String(format: "%02d:%02d", min, seconds)
}
}
typealias Output = TimeRemaining
typealias Failure = Never
let duration: TimeInterval
func receive<S>(subscriber: S) where S : Subscriber, Never == S.Failure, Output == S.Input {
let subscription = CountDownSubscription(duration: duration, subscriber: subscriber)
subscriber.receive(subscription: subscription)
}
}
private extension CountDownTimer {
class CountDownSubscription<S: Subscriber>: Subscription where S.Input == Output, S.Failure == Failure {
private var duration: Int
private var subscriber: S?
private var timer: Timer?
init(duration: TimeInterval, subscriber: S) {
self.duration = Int(duration)
self.subscriber = subscriber
}
func request(_ demand: Subscribers.Demand) {
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { timer in
self.duration -= 1
if self.duration == 0 {
self.subscriber?.receive(completion: .finished)
} else {
let components = self.durationToTimeComponents(self.duration)
let timeRemaining = TimeRemaining(min: components.min, seconds: components.seconds, totalSeconds: self.duration)
_ = self.subscriber?.receive(timeRemaining)
}
})
timer?.fire()
}
func cancel() {
timer?.invalidate()
}
func durationToTimeComponents(_ duration: Int) ->(min: Int, seconds: Int) {
return (min: duration / 60, seconds: duration % 60)
}
}
}
@kaunteya
Copy link
Author

Usage

// Prints the time remaining at in interval of 1 second
let timer: AnyCancellable = CountDownTimer(duration: 20)
    .sink { val in
        print(val)
    }

@erickva
Copy link

erickva commented Mar 18, 2022

Thanks for sharing @kaunteya, very useful.
I am using if self.duration < 0 so my counter goes all the way to 0.
And also calling self.cancel() after the completion: .finished to stop the timer and execution.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment