Introducing RxAnimated
31/Oct 2017
RxCocoa is super powerful for UI bindings - you can use bind(to:)
to bind pretty much any kind of observable to a UI Control of your choice. You can bind a String
to a UILabel
, UIImage
to a UIImageView
, or an array of objects to UITableView
or UICollectionView
.
Now, for table and collection view bindings there is a special library to allow you to bind a list of objects called RxDataSources
and besides all the other goodness the library can add animations to your bindings.
And by god it makes all the difference in the world… So, I had the idea to bring the possibility to animate bindings also to other controls besides table and collection views for a while, but just recently found the time to look in more detail in implementing something re-usable.
Introducing RxAnimated
RxAnimated is a newly released library for RxSwift/RxCocoa, which allows you to:
- add simple pre-built transitions to your bindings, like fade and flip
- add arbitrary block-based animations to your transitions
- extend RxAnimated to support new bindings sinks in your own classes
- extend RxAnimated to support new your own custom animations
In this post I’m going to show how to get started with RxAnimated. In short the goal of the library is to slightly change existing binding code so that instead of instant changes it adds animations to the UI.
Non-animated binding code
To bind an observable to a label in your UI you would usually do something like this:
import RxCocoa
...
counterObservable
.bind(to: label.rx.text)
The binding results in instant changes in the UI like so:
Animated binding with RxAnimated
To add a simple flip transition to the same binding you need to import RxAnimated and change a bit the code like so:
import RxCocoa
import RxAnimated
...
counterObservable
.bind(animated: label.rx.animated.flip(.top, duration: 0.33).text)
I struggled to create a very idiomatic API (and had some very fruitful discussions in the RxSwift slack) so you can with as little change add animations. You basically take the existing binding sink and insert the type of transition you want:
With this change the binding triggers the transition each time it needs to produce side effects (e.g. in that case update the text of the label):
API design
The API is designed to be very flexible and I’m going to highlight just few points here:
Animations are constrained to the UI component type
The build-in animations like fade and flip are constrained to UIView
so you can use them on all views. If we look at the previous example:
counterObservable
.bind(animated: label.rx.animated.flip(.top, duration: 0.33).text)
You would add the same animation in the same way to an image view:
imagesObservable
.bind(animated: imageView.rx.animated.flip(.top, duration: 0.33).image)
This will produce the same animation any time a new image is emitted by the observable:
The binding type or the observable type does not restrict the animation you apply.
If you do want however to create your own animation and restrict it only to UILabel
and its sub-classes you can do that and the API will not make it available for bindings to any other types.
You can add new binding sinks and animations via protocols
RxAnimated includes a list of bindings you can use out of the box but what if you have a custom view with its custom properties, which you bind to and want to animate?
Let’s look at the implementation of UIView.rx.animated ... isHidden
:
extension AnimatedSink where Base: UIView {
public var isHidden: Binder<Bool> {
return Binder(self.base) { view, hidden in
self.type.animate(view: view, binding: {
view.isHidden = hidden
})
}
}
}
AnimatedSink
is the type, which adds the rx.animated
namespace to UI components. You extend it and make sure to set the Base
to your target type. Then you add a property for your binding sink (above that’s isHidden
), which creates a new Binder
instance. Inside the Binder
s closure parameter you call self.type.animate(...)
to create an animation.
Finally inside the closure you do the UI updates you want to have animated. In the example above that’s simply setting view.isHidden
to a new value but it can be anything you want - you can transform the view, update properties, etc.
You can add new custom animations in a similar fashion, just have a look at the code.
What comes out of the box?
Here’s a list of the built-in animated sinks:
UIView
.rx.animated...isHidden
.rx.animated...alpha
UILabel
.rx.animated...text
.rx.animated...attributedText
UIControl
.rx.animated...isEnabled
.rx.animated...isSelected
UIButton
.rx.animated...title
.rx.animated...image
.rx.animated...backgroundImage
UIImageView.rx.animated...image
NSLayoutConstraint
.rx.animated...constant
.rx.animated...isActive
List of the built-in animations:
UIView
.rx.animated.fade(duration: TimeInterval)
.rx.animated.flip(FlipDirection, duration: TimeInterval)
.rx.animated.tick(FlipDirection, duration: TimeInterval)
.rx.animated.animation(duration: TimeInterval, animations: ()->Void)
NSLayoutConstraint
.rx.animated.layout(duration: TimeInterval)
Why only animated bindings?
Maybe some of you will ask “What if I want to create an arbitrary animation when my observable emits a value?” Well the good news is - you can! In observe(onNext: {...})
is the place to perform any side effects your heart desires, including animations. For the moment I don’t see big benefit (or a clear way to) create some animation API, which will add value to creating animations via observe(onNext:..)
.
Bindings on the other hand are a useful feature, which benefits of simple transition animations and I think RxAnimated adds a lot of value there.
Where to go from here?
RxAnimated is available for free on GitHub: https://github.com/RxSwiftCommunity/RxAnimated and can be installed via CocoaPods like so pod "RxAnimated"
. It supports RxSwift4+ and I’d appreciate if you have any feedback regarding the library. As always you can reach me at @icanzilb.
The best way to get started is to check the code in the repo example app which demoes various types of bindings and animations:
To learn more about creating RxCocoa bindings and RxSwift in general check out the RxBook! The book is available at http://raywenderlich.com/store - this is where you can see any updates, discuss in the website forums, etc.
Hope that post was helpful, and if you want to get in touch you can find me here Follow @icanzilb
Share this post: Tweet