Rodrigo Ruiz Rodrigo Ruiz - 3 months ago 14
Swift Question

How to user ReactiveX to execute async's in sequence

How do I leverage ReactiveX to execute async calls in sequence?
I.e., execute a second call after first one has finished.

More specifically, I'm working with RxSwift in iOS, and the asyncs I want to chain together are

UIView
animations (instead of calling the second animation inside the
completion
block of the first one).

I know I have other options like Easy Animation, but I'd like to leverage Rx, since I'm already using it for streams.

Also, one solution would be (for 3 chained animations):

_ = UIView.animate(duration: 0.2, animations: {
sender.transform = CGAffineTransformMakeScale(1.8, 1.8)
})
.flatMap({ _ in
return UIView.animate(duration: 0.2, animations: {
sender.transform = CGAffineTransformMakeScale(0.8, 0.8)
})
})
.flatMap({ _ in
return UIView.animate(duration: 0.2, animations: {
sender.transform = CGAffineTransformIdentity
})
})
.subscribeNext({ _ in })


But I'm looking for something more elegant, the right way of doing it with Rx.

Answer

I don't think using Rx makes it much cleaner, but here's how you could do it:

let animation1 = Observable<Void>.create { observer in
    UIView.animateWithDuration(0.2,
        animations: {
            // do your animations
        }, completion: { _ in
            observer.onCompleted()
        })
    return NopDisposable.instance
}

let animation2 = Observable<Void>.create { observer in
    UIView.animateWithDuration(0.2,
        animations: {
            // do your animations
        }, completion: { _ in
            observer.onCompleted()
        })
    return NopDisposable.instance
}

Observable.of(animation1, animation2)
    .concat()
    .subscribe()
    .addDisposableTo(disposeBag)

It would also be cleaner if you create a function to construct the Observable<Void>s for you.

func animation(duration: NSTimeInterval, animations: () -> Void) -> Observable<Void> {
    return Observable<Void>.create { observer in
        UIView.animateWithDuration(duration,
            animations: animations,
            completion: { _ in
                observer.onCompleted()
            })
        return NopDisposable.instance
    }

I guess a plus side to using Rx instead of just animateWithDuration:animations: chained, is that you don't have to nest the animations in completion blocks. This way, you can just define them on their own and compose them as you want afterward.

As an alternative to RxSwift, check out PromiseKit. RxSwift is a bit overkill for your animation callback needs. This blog post in particular is relevant.