Countdown Timer by 'Rxswift'

countdown timer by `RxSwift`

Better approach for existing answer.

let countDown = 15 // 15 seconds 
Observable<Int>.timer(.seconds(0), period: .seconds(1), scheduler: MainScheduler.instance)
.take(countDown+1)
.subscribe(onNext: { timePassed in
let count = self.countDown - timePassed
print(count)

}, onCompleted: {
print("count down complete")
})

RxSwift - start and update count down timer

What you're trying to achieve kinda sounds like a state machine.

You could achieve it by splitting the timer actions into actual "Actions" and merging them based on the trigger (one being the manual "add two seconds", and thee other is automated as "reduce one second").

I haven't fully tested it, but this can be a good starting ground:

enum TimerAction {
case tick
case addTwoSeconds
}

let trigger = PublishRelay<Void>()

let timer = Observable<Int>
.interval(.seconds(1), scheduler: MainScheduler.instance)
.map { _ in TimerAction.tick }

let addSeconds = trigger.map { TimerAction.addTwoSeconds }

Observable
.merge(timer, addSeconds)
.scan(into: 15) { totalSeconds, action in
totalSeconds += action == .addTwoSeconds ? 2 : -1
}
.takeUntil(.inclusive) { $0 == 0 }
.subscribe()
.disposed(by: disposeBag)

DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
trigger.accept(()) // increase the timer by two seconds after 5 seconds
}

RxSwift Countdown time up to 0.1 seconds

Use .milliseconds(100) instead of .seconds(1)

How do I make a resettable RxSwift Timer?

Update

In the comments, I was asked to justify why I suggested making a test for "new code". Part of the answer was that you should never accept the first draft of your code. As any composition teacher would tell you, don't turn in your first draft, spend some time refining what you write (with peer review if you can get it.) So given that and the fact that my tests missed one of the specifications, I was going to replace my initial answer with this more refined version, but I think it is instructive to keep the original so it can be compared to the refined answer.

In the below, you will see that I have updated the tests to accommodate the new specification and refined the code.

The fact that there is a flatMap in the function implies that there are two abstractions here. So I broke that out into a separate function.

The fact that I have enums with two case implies that I could use a Bool instead and remove the switches.

class rx_sandboxTests: XCTestCase {

func testPause() {
let scheduler = TestScheduler(initialClock: 0)
let pause = scheduler.createColdObservable([.next(10, ()), .next(20, ())])
let reset = scheduler.createColdObservable([.next(30, ())])
let result = scheduler.start {
isPaused(pause: pause.asObservable(), reset: reset.asObservable())
}
XCTAssertEqual(result.events, [.next(200, true), .next(210, false), .next(220, true)])
}

func testTimerStart() {
let scheduler = TestScheduler(initialClock: 0)
let pause = scheduler.createColdObservable([.next(10, ())])
let reset = scheduler.createColdObservable([Recorded<Event<Void>>]())

let result = scheduler.start {
timer(initial: 10, pause: pause.asObservable(), reset: reset.asObservable(), scheduler: scheduler)
}

XCTAssertEqual(result.events, [.next(200, 10), .next(211, 9), .next(212, 8), .next(213, 7), .next(214, 6), .next(215, 5), .next(216, 4), .next(217, 3), .next(218, 2), .next(219, 1), .next(220, 0)])
}

func testPausedTimer() {
let scheduler = TestScheduler(initialClock: 0)
let pause = scheduler.createColdObservable([.next(10, ()), .next(13, ()), .next(20, ())])
let reset = scheduler.createColdObservable([Recorded<Event<Void>>]())

let result = scheduler.start {
timer(initial: 4, pause: pause.asObservable(), reset: reset.asObservable(), scheduler: scheduler)
}

XCTAssertEqual(result.events, [.next(200, 4), .next(211, 3), .next(212, 2), .next(221, 1), .next(222, 0)])
}

func testResetBeforeStarting() {
let scheduler = TestScheduler(initialClock: 0)
let pause = scheduler.createColdObservable([.next(20, ())])
let reset = scheduler.createColdObservable([.next(10, ())])

let result = scheduler.start {
timer(initial: 3, pause: pause.asObservable(), reset: reset.asObservable(), scheduler: scheduler)
}

XCTAssertEqual(result.events, [.next(200, 3), .next(221, 2), .next(222, 1), .next(223, 0)])
}

func testResetWhileRunning() {
let scheduler = TestScheduler(initialClock: 0)
let pause = scheduler.createColdObservable([.next(10, ()), .next(20, ())])
let reset = scheduler.createColdObservable([.next(13, ())])

let result = scheduler.start {
timer(initial: 4, pause: pause.asObservable(), reset: reset.asObservable(), scheduler: scheduler)
}

XCTAssertEqual(result.events, [.next(200, 4), .next(211, 3), .next(212, 2), .next(213, 4), .next(221, 3), .next(222, 2), .next(223, 1), .next(224, 0)])
}

func testResetWhilePaused() {
let scheduler = TestScheduler(initialClock: 0)
let pause = scheduler.createColdObservable([.next(10, ()), .next(13, ()), .next(20, ())])
let reset = scheduler.createColdObservable([.next(15, ())])

let result = scheduler.start {
timer(initial: 4, pause: pause.asObservable(), reset: reset.asObservable(), scheduler: scheduler)
}

XCTAssertEqual(result.events, [.next(200, 4), .next(211, 3), .next(212, 2), .next(215, 4), .next(221, 3), .next(222, 2), .next(223, 1), .next(224, 0)])
}

func testResetWhenEnded() {
let scheduler = TestScheduler(initialClock: 0)
let pause = scheduler.createColdObservable([.next(10, ()), .next(20, ())])
let reset = scheduler.createColdObservable([.next(15, ())])

let result = scheduler.start {
timer(initial: 4, pause: pause.asObservable(), reset: reset.asObservable(), scheduler: scheduler)
}

XCTAssertEqual(result.events, [.next(200, 4), .next(211, 3), .next(212, 2), .next(213, 1), .next(214, 0), .next(215, 4), .next(221, 3), .next(222, 2), .next(223, 1), .next(224, 0)])
}
}

func timer(initial: Int, pause: Observable<Void>, reset: Observable<Void>, scheduler: SchedulerType) -> Observable<Int> {
let tick = isPaused(pause: pause, reset: reset)
.flatMapLatest { $0 ? .empty() : Observable<Int>.interval(.seconds(1), scheduler: scheduler).take(initial) }

return ticker(initial: initial, tick: tick, reset: reset)
}

func isPaused(pause: Observable<Void>, reset: Observable<Void>) -> Observable<Bool> {
Observable.merge(pause.map { false }, reset.map { true })
.scan(true) { $1 || !$0 }
.startWith(true)
.distinctUntilChanged()
}

func ticker<T>(initial: Int, tick: Observable<T>, reset: Observable<Void>) -> Observable<Int> {
return Observable.merge(tick.map { _ in false }, reset.map { true })
.scan(initial) { $1 ? initial : $0 - 1 }
.startWith(initial)
.filter { 0 <= $0 }
.distinctUntilChanged()
}

Original Answer Follows:

I changed your pause from an Observable<Bool> to Observable<Void>. The Bool didn't make any sense because the reset can also cause a pause and that would conflict with the other observable.

Here's the complete code, including a test harness:

class rx_sandboxTests: XCTestCase {

func testTimerStart() {
let scheduler = TestScheduler(initialClock: 0)
let pause = scheduler.createColdObservable([.next(10, ())])
let reset = scheduler.createColdObservable([Recorded<Event<Void>>]())

let result = scheduler.start {
timer(initial: 10, pause: pause.asObservable(), reset: reset.asObservable(), scheduler: scheduler)
}

XCTAssertEqual(result.events, [.next(211, 9), .next(212, 8), .next(213, 7), .next(214, 6), .next(215, 5), .next(216, 4), .next(217, 3), .next(218, 2), .next(219, 1), .next(220, 0)])
}

func testPause() {
let scheduler = TestScheduler(initialClock: 0)
let pause = scheduler.createColdObservable([.next(10, ()), .next(13, ()), .next(20, ())])
let reset = scheduler.createColdObservable([Recorded<Event<Void>>]())

let result = scheduler.start {
timer(initial: 4, pause: pause.asObservable(), reset: reset.asObservable(), scheduler: scheduler)
}

XCTAssertEqual(result.events, [.next(211, 3), .next(212, 2), .next(221, 1), .next(222, 0)])
}

func testResetBeforeStarting() {
let scheduler = TestScheduler(initialClock: 0)
let pause = scheduler.createColdObservable([.next(20, ())])
let reset = scheduler.createColdObservable([.next(10, ())])

let result = scheduler.start {
timer(initial: 3, pause: pause.asObservable(), reset: reset.asObservable(), scheduler: scheduler)
}

XCTAssertEqual(result.events, [.next(221, 2), .next(222, 1), .next(223, 0)])
}

func testResetWhileRunning() {
let scheduler = TestScheduler(initialClock: 0)
let pause = scheduler.createColdObservable([.next(10, ()), .next(20, ())])
let reset = scheduler.createColdObservable([.next(13, ())])

let result = scheduler.start {
timer(initial: 4, pause: pause.asObservable(), reset: reset.asObservable(), scheduler: scheduler)
}

XCTAssertEqual(result.events, [.next(211, 3), .next(212, 2), .next(221, 3), .next(222, 2), .next(223, 1), .next(224, 0)])
}

func testResetWhilePaused() {
let scheduler = TestScheduler(initialClock: 0)
let pause = scheduler.createColdObservable([.next(10, ()), .next(13, ()), .next(20, ())])
let reset = scheduler.createColdObservable([.next(15, ())])

let result = scheduler.start {
timer(initial: 4, pause: pause.asObservable(), reset: reset.asObservable(), scheduler: scheduler)
}

XCTAssertEqual(result.events, [.next(211, 3), .next(212, 2), .next(221, 3), .next(222, 2), .next(223, 1), .next(224, 0)])
}

func testResetWhenEnded() {
let scheduler = TestScheduler(initialClock: 0)
let pause = scheduler.createColdObservable([.next(10, ()), .next(20, ())])
let reset = scheduler.createColdObservable([.next(15, ())])

let result = scheduler.start {
timer(initial: 4, pause: pause.asObservable(), reset: reset.asObservable(), scheduler: scheduler)
}

XCTAssertEqual(result.events, [.next(211, 3), .next(212, 2), .next(213, 1), .next(214, 0), .next(221, 3), .next(222, 2), .next(223, 1), .next(224, 0)])
}
}

func timer(initial: Int, pause: Observable<Void>, reset: Observable<Void>, scheduler: SchedulerType) -> Observable<Int> {
enum Action { case pause, reset, tick }
let intent = Observable.merge(
pause.map { Action.pause },
reset.map { Action.reset }
)

let isPaused = intent
.scan(true) { isPaused, action in
switch action {
case .pause:
return !isPaused
case .reset:
return true
case .tick:
fatalError()
}
}
.startWith(true)

let tick = isPaused
.flatMapLatest { $0 ? .empty() : Observable<Int>.interval(.seconds(1), scheduler: scheduler) }

return Observable.merge(tick.map { _ in Action.tick }, reset.map { Action.reset })
.scan(initial) { (current, action) -> Int in
switch action {
case .pause:
fatalError()
case .reset:
return initial
case .tick:
return current == -1 ? -1 : current - 1
}

}
.filter { 0 <= $0 && $0 < initial }
}

It's good to know how to test Rx code.

How to reset and start new timer when enter new email

The key is using flatMapLatest to cancel the previous timer and start up a new one.

Based on your description, here is what you need:

struct Output {
let eventResendEmailCountdown: Observable<Int>
let eventShowHideResendEmailButton: Observable<Bool>
let eventDismissCountdownBottomSheet: Observable<Void>
}

func example(text: Observable<String?>) -> Output {
let trigger = text.share()
let eventResendEmailCountdown = trigger
.flatMapLatest { _ in
Observable<Int>.interval(.seconds(1), scheduler: MainScheduler.instance)
.map { 120 - $0 }
.take(until: { $0 == -1 })
}
let eventShowHideResendEmailButton = Observable.merge(
trigger.map { _ in false },
eventResendEmailCountdown.filter { $0 == 0 }.map { _ in true }
)
let eventDismissCountdownBottomSheet = eventResendEmailCountdown
.filter { $0 == 0 }
.map { _ in }

return Output(
eventResendEmailCountdown: eventResendEmailCountdown,
eventShowHideResendEmailButton: eventShowHideResendEmailButton,
eventDismissCountdownBottomSheet: eventDismissCountdownBottomSheet
)
}

Call it something like this:

let output = example(text: textField.rx.text.asObservable())

output.eventResendEmailCountdown
.debug("eventResendEmailCountdown")
.subscribe()

output.eventShowHideResendEmailButton
.debug("eventShowHideResendEmailButton")
.subscribe()

output.eventDismissCountdownBottomSheet
.debug("eventDismissCountdownBottomSheet")
.subscribe()

How to reset the Observable interval operator in RXSwift?

Just remove(dispose) old subscription and make new subscription with new interval

var timerDisposable:Disposable?
var retryTime:RxTimeInterval = 1

func stratRefresh() {
timerDisposable?.dispose()
timerDisposable = Observable<Int>
.timer(0, period: retryTime, scheduler: MainScheduler.instance)
.subscribe(onNext: { value in
myWebserviceMethod()
})
}

func myWebserviceMethod() {
// In service response update your retryTime
// Ex.
APIClient.getRetryTime() { newTime in
if retryTime != newTime {
retryTime = newTime
stratRefresh()
}
}
}

Combine RxSwift timer and interval observables into a single observable with completion

Is that like this?

CountdownTimer.swift

var timer = CountdownTimer(5)
var count = 0

func setupCountdownTimer() {
timer.observable >- subscribeNext { n in
println(n) // "5", "4", ..., "0"
self.count = n
}
timer.observable >- subscribeCompleted {
println(self.count)
}
}

@IBAction func stop(sender: UIButton) {
timer.sendCompleted()
}

Rx Swift simple timer not working

DisposeBag will dispose of your subscription once it goes out of scope. In this instance, it'll be right after the call to subscribe, and it explains why you don't see anything printed to the console.

Move the definition of dispose bag to the class creating the subscription and everything should work fine.

class MyViewController: UIViewController {
let bag:DisposeBag = DisposeBag()

dynamic func onButtonTapped() {
var sig:Observable<Int>!
sig = Observable<Int>.interval(1.0, scheduler: MainScheduler.instance)

sig.subscribe(onNext: { (sec) in
print("Sec: \(sec)")
}).addDisposableTo(bag)
}
}

On a side note, interval expects an interval in seconds, so it will only tick every seconds as oposed to milliseconds.



Related Topics



Leave a reply



Submit