Search code examples
iosswiftasynchronousrx-swiftreactivex

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.


Solution

  • 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.