Binding custom view properties with RxSwift

Lately, I have been dipping my toes into the world of reactive programming. To be honest, I should probably say that I’m up to my neck into it: ReactiveCocoa, MVVM, “everything is a stream”, and all that.

In my spare time I’m looking also into RxSwift. Even though this blog is mostly about Swift, I spend a significant amount of time writing code in other languages (mostly Java, Scala and C#), so it makes sense to learn what seems to be a more cross-platform approach than the one ReactiveCocoa follows. Platform agnosticism FTW!

But, back to the point of this post. Remember the Ring we discussed in the previous post? If you don’t, well, it’s basically a UIView subclass, that exposes a few public properties to make it configurable. Today I am going to focus on just one of them: progress.

The Ring class is part of the UI of a little pet project of mine, where I want to display some data I fetch from an async service. After fetching that data, I want to update a progress ring (my Ring class) and a label.



So, in the good old days, before my reactive enlightenment I would probably do something like this: (for context, this is the view controller that manages the ring and the label)

private func fetchData() {
    dataService.stepCount(NSDate(), endDate: NSDate(), completion: { steps in
    label.text = "\(steps)"
    ring.progress = CGFloat(steps) / Constans.dailyGoal

For the sake of completing the example, the signature of the method in the data service would be like:

func setpCount(initalDate: NSDate, endDate: NSDate, completion: ()->Int) {}

So, not too bad, I guess. An async service, a completion closure as a parameter, and done.

But, if there is a use case for reactive programming, that would be dealing with async stuff. So, let’s go Rx!

The method in the data service signature could look like this:

func stepCount(initDate: NSDate, endDate: NSDate) -> Observable

And now, in my view controller, I could do something like:

private func fetchData() {
    dataService.stepCount(NSDate(), endDate: NSDate()).subscribeNext { result in
        self.label.text = "\(result)"
        self.ring.progress = CGFloat(result) / Constants.goal

Not too bad! But it could be better. This code is still a little bit imperative. It is not a big deal, but I think it would look better, be more readable, and in a way easier to maintain, if I could bind both the ring and the label to the result emitted by the data service observable.

Binding to a UILabel is easy, RxCocoa provides extensions for that, but binding to a custom property in a custom view is obviously not provided out of the box.

But fear not, because it is very easy to do. As easy as the following:

extension Ring {
    public var rx_ring_progress: AnyObserver {
        return UIBindingObserver(UIElement: self) { view, progress in
        view.progress = progress

So now, back to my view controller, I can bind the Observable directly to the ui controls, transforming the values emitted by the data service, in just one go:

let data = dataService.stepCount(NSDate(), endDate: NSDate()){"\($0)"}.bindTo(label.rx_text).addDisposableTo(disposeBag){CGFloat($0) / Constants.goal}.bindTo(ring.rx_ring_progress).addDisposableTo(disposeBag)

And done!

Leave a Reply

Your email address will not be published. Required fields are marked *