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
Nscollectionviewitem Never Instantiate
Can You Enforce a Typealias in Swift
Error with Parse Query Findobjectsinbackgroundwithblock
Why Can't I Use Subscripting on a Ckrecord Object in Swift
Why Does Somestruct() Is Anyobject Return True
Concatenate Literal with Optional String
How to Handle Parameter Validation Swift
Images Inaccessible from Asset Catalog in a Swiftui Framework
Cannot Invoke Initializer for Type 'Sqlite3_Destructor_Type'
Count the Number of Lines in a Swift String
Scenekit Shape Between 4 Points
Why Do Two Distinct Array Literals Equal Each Other in Swift
How to Incorporate Swift Package Manager to an Existing Xcode Project
Why Does My @Lazy Property Crash, But If I Make It Non Lazy It Works
How to Cast Generic Number Type 'T' to Cgfloat
Access Flutter Sharedpreferences in Swift
"Message from Debugger: Unable to Attach" When Running Tests on Osx App
Why There Is a Overflow with Swift Language When Assign a 8 Bits Binary Value to a Var of Int8 Type