Edit 18.01.2017: This post was updated to Swift 3.0 and RxSwift 3.1

In the first chapter we’ve learned the basics about RxSwift and RxCocoa (if you haven’t seen it yet, I really encourage you to do so!) . The time has come and we will expand our knowledge in a reactive way. Today we will talk about bindings.

Don’t worry, binding just means connecting and we will connect our Observables with Subjects. There is some terminology that we haven’t learned before, so…

Definitions

Before we start we need to get in touch with some definitions. We learned about Observables and Observers and today we will learn about other types.

Subject – Observable and Observer at once. Basically it can observe and be observed.

BehaviorSubject – When you subscribe to it, you will get the latest value emitted by the Subject, and then the values emitted after the subscription.

PublishSubject – When you subscribe to it, you will only get the values that were emitted after the subscription.

ReplaySubject – When you subscribe to it, you will get the values that were emitted after the subscription, but also values that were emitted before the subscription. How many old values will you get? It depends on the buffer size of ReplaySubject you subscribe to. You specify it in init of the Subject.

Okay, okay. Too many subjects. Let’s simplify it a little bit. You are having a birthday party ? and you are opening the presents you’ve got.

You’ve opened first, second, third gift. And whoops! Your mom was cooking some delicious food and is late to the opening party. As a mom, she just has to know what presents you’ve got already. So you tell her about them. In Rx world you’ve sent observable sequence (presents) to the observer (your mom). What’s interesting is that she started observing you after you already emitted few values, but she got whole info anyways. For her we are a ReplaySubject with buffer = 3 (we save 3 latest presents and give it every time a new subscriber appears).

You are still opening presents and there you see that two of your friends (Jack and Andy) were also late to the party. Jack happens to be your close friend so he asks what have you opened so far. As you are kinda angry that he missed a part of it, you tell him only the latest present you’ve opened. He doesn’t know that there were more of it, so he is happy with it. In Rx world you’ve sent only latest emitted value to the observer (Jack). He will also get the next values as you emit them (next presents you will open). For him we are a BehaviorSubject (we kinda changed the Subject ?).

There is also Andy, who happens to be just a friend and doesn’t really care about the presents you’ve opened so he just sits down and waits for the rest of the show. As you can imagine, for him we are just a PublishSubject. He just gets values that are emitted after the subscription.

There is also something called Variable. This is wrapper around BehaviorSubject. The thing is, you can only submit the .onNext() event (when using BehaviorSubject you have direct access to sending .onError() , .onCompleted() ). Also, Variable automatically sends .onCompleted() event when it’s being deallocated.

Alright, enough with definitions. Let’s try it out!

Example

We will create simple app that will connect ball color with position in view and also we will connect view’s background color with the ball color.

First let’s create a project as we created in a tutorial before. We will also use CocoaPods and in addition to RxSwift and RxCocoa we will use Chameleon to nicely connect the colors. Our Podfile should look like this:

platform :ios, '9.0' use_frameworks! target 'ColourfulBall' do pod 'RxSwift' pod 'RxCocoa' pod 'ChameleonFramework/Swift', :git => 'https://github.com/ViccAlexander/Chameleon.git' end post_install do |installer| installer.pods_project.targets.each do |target| target.build_configurations.each do |config| config.build_settings['ENABLE_TESTABILITY'] = 'YES' config.build_settings['SWIFT_VERSION'] = '3.0' end end end 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 platform : ios , '9.0' use_frameworks ! target 'ColourfulBall' do pod 'RxSwift' pod 'RxCocoa' pod 'ChameleonFramework/Swift' , : git = > 'https://github.com/ViccAlexander/Chameleon.git' end post_install do | installer | installer . pods_project . targets . each do | target | target . build_configurations . each do | config | config . build_settings [ 'ENABLE_TESTABILITY' ] = 'YES' config . build_settings [ 'SWIFT_VERSION' ] = '3.0' end end end

After setting up the project we can start coding! First we will draw circle in the main view of our controller. We will do it from code, but if you want to do it in Interface Builder – you are free to go. Example of creating that view looks like one below:

import ChameleonFramework import UIKit import RxSwift import RxCocoa class ViewController: UIViewController { var circleView: UIView! override func viewDidLoad() { super.viewDidLoad() setup() } func setup() { // Add circle view circleView = UIView(frame: CGRect(origin: view.center, size: CGSize(width: 100.0, height: 100.0))) circleView.layer.cornerRadius = circleView.frame.width / 2.0 circleView.center = view.center circleView.backgroundColor = .green view.addSubview(circleView) } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import ChameleonFramework import UIKit import RxSwift import RxCocoa class ViewController : UIViewController { var circleView : UIView ! override func viewDidLoad ( ) { super . viewDidLoad ( ) setup ( ) } func setup ( ) { // Add circle view circleView = UIView ( frame : CGRect ( origin : view . center , size : CGSize ( width : 100.0 , height : 100.0 ) ) ) circleView . layer . cornerRadius = circleView . frame . width / 2.0 circleView . center = view . center circleView . backgroundColor = . green view . addSubview ( circleView ) } }

That code should be self-explanatory (we just created rounded UIView) so we will just move forward. Next step would be to move our ball on pan gesture. In order to do that let’s add UIPanGestureRecognizer and change the frame of it:

func setup() { // Add circle view circleView = UIView(frame: CGRect(origin: view.center, size: CGSize(width: 100.0, height: 100.0))) circleView.layer.cornerRadius = circleView.frame.width / 2.0 circleView.center = view.center circleView.backgroundColor = .green view.addSubview(circleView) // Add gesture recognizer let gestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(circleMoved(_:))) circleView.addGestureRecognizer(gestureRecognizer) } func circleMoved(_ recognizer: UIPanGestureRecognizer) { let location = recognizer.location(in: view) UIView.animateWithDuration(0.1) { self.circleView.center = location } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 func setup ( ) { // Add circle view circleView = UIView ( frame : CGRect ( origin : view . center , size : CGSize ( width : 100.0 , height : 100.0 ) ) ) circleView . layer . cornerRadius = circleView . frame . width / 2.0 circleView . center = view . center circleView . backgroundColor = . green view . addSubview ( circleView ) // Add gesture recognizer let gestureRecognizer = UIPanGestureRecognizer ( target : self , action : #selector(circleMoved(_:))) circleView . addGestureRecognizer ( gestureRecognizer ) } func circleMoved ( _ recognizer : UIPanGestureRecognizer ) { let location = recognizer . location ( in : view ) UIView . animateWithDuration ( 0.1 ) { self . circleView . center = location } }

Perfect! Our app should now look somewhat similar to the one below:

The next step would be to bind something! Let’s connect position of the ball with ball’s color. How to do that? First we will observe ball’s center position using rx.observe() and then bind it to a Variable, using bindTo() . But what does binding do in our case? Well, every time a new position is emitted by our ball, the variable will receive a new signal about it. In this case our variable is an Observer, because it will observe the position.

We will create this variable in a ViewModel, which will be used to calculate UI things. In this case every time our variable will get a new position, we will calculate new ball’s background color. Easy, right?

LET’S TALK ABOUT YOUR APP We’re 100% office based team with 7-years’ experience

in mobile & web app development Estimate project

Now we need to create our ViewModel. It will be a really simple one, because we will have only 2 properties: centerVariable which will be our observer & observable – we will save data to it and we will get it. And the second one will be backgroundColorObservable . It is actually not a Variable, but only an Observable.

Now you might ask “Why is centerVariable a Variable, but backgroundColorObservable is an Observable?” And that is a brilliant question! See, our observable center of ball is connected with centerVariable . It means that overtime the center changes, centerVariable will get the change. It is then an Observer. Also in our ViewModel we use centerVariable as an Observable, which makes it both Observer and Observable which is just a Subject. Why Variable and not PublishSubject, ReplaySubject? Because we want to be sure we will get the latest center of that ball every time we subscribe to it.

backgroundColorObservable is just an Observable, it is never bound to anything so it makes perfect sense to leave it just as an Observable.

Alright! Done with the theory, let’s code! Our basic ViewModel should look like this:

import ChameleonFramework import Foundation import RxSwift import RxCocoa class CircleViewModel { var centerVariable = Variable<CGPoint?>(.zero) // Create one variable that will be changed and observed var backgroundColorObservable: Observable! // Create observable that will change backgroundColor based on center init() { setup() } setup() { } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import ChameleonFramework import Foundation import RxSwift import RxCocoa class CircleViewModel { var centerVariable = Variable < CGPoint ? > ( . zero ) // Create one variable that will be changed and observed var backgroundColorObservable : Observable ! // Create observable that will change backgroundColor based on center init ( ) { setup ( ) } setup ( ) { } }

Perfect. Now we need to setup our backgroundColorObservable . We want it to change based on new CGPoint produced by centerVariable .

func setup() { // When we get new center, emit new UIColor backgroundColorObservable = centerVariable.asObservable() .map { center in guard let center = center else { return UIColor.flatten(.black)() } let red: CGFloat = ((center.x + center.y) % 255.0) / 255.0 // We just manipulate red, but you can do w/e let green: CGFloat = 0.0 let blue: CGFloat = 0.0 return UIColor.flatten(UIColor(red: red, green: green, blue: blue, alpha: 1.0))() } } 1 2 3 4 5 6 7 8 9 10 11 12 13 func setup ( ) { // When we get new center, emit new UIColor backgroundColorObservable = centerVariable . asObservable ( ) . map { center in guard let center = center else { return UIColor . flatten ( . black ) ( ) } let red : CGFloat = ( ( center . x + center . y ) % 255.0 ) / 255.0 // We just manipulate red, but you can do w/e let green : CGFloat = 0.0 let blue : CGFloat = 0.0 return UIColor . flatten ( UIColor ( red : red , green : green , blue : blue , alpha : 1.0 ) ) ( ) } }

Step by step:

Transform our variable into Observable – since Variable can be both Observer and Observable, we need to decide which one is it. And since we want to observe it, we transform it into Observable. Map every new value of CGPoint to UIColor . We get the new center that our Observable produced, then based on (not-so) really complicated math calculations we create new UIColor . You may notice that our Observable is an optional CGPoint . Why? We will explain it in a second. But we need to protect ourselves and in case we get nil, return some default color (black in our case).

Okay. We are really close to the end. We have now Observable that will emit new background color for a ball. We just need to update our ball based on the new values. Now that’s really easy. It’s similar to our part #1 of the series. We will subscribe() to the Observable.

// Subscribe to backgroundObservable to get new colors from the ViewModel. circleViewModel.backgroundColorObservable .subscribe(onNext: { [weak self] backgroundColor in UIView.animateWithDuration(0.1) { self?.circleView.backgroundColor = backgroundColor // Try to get complementary color for given background color let viewBackgroundColor = UIColor(complementaryFlatColorOf: backgroundColor) // If it is different that the color if viewBackgroundColor != backgroundColor { // Assign it as a background color of the view // We only want different color to be able to see that circle in a view self?.view.backgroundColor = viewBackgroundColor } } }) .addDisposableTo(disposeBag) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 // Subscribe to backgroundObservable to get new colors from the ViewModel. circleViewModel . backgroundColorObservable . subscribe ( onNext : { [ weak self ] backgroundColor in UIView . animateWithDuration ( 0.1 ) { self ? . circleView . backgroundColor = backgroundColor // Try to get complementary color for given background color let viewBackgroundColor = UIColor ( complementaryFlatColorOf : backgroundColor ) // If it is different that the color if viewBackgroundColor ! = backgroundColor { // Assign it as a background color of the view // We only want different color to be able to see that circle in a view self ? . view . backgroundColor = viewBackgroundColor } } } ) . addDisposableTo ( disposeBag )

As you can see we’ve also added changing the background color of our view to the complementary color of our ball. Also we have the check if complementary color is the same as the balls’ color (we want to see it at least!). But that’s a feature, not that main task. You need to add this code rather in the setup() method, so it looks similar to:

func setup() { // Add circle view circleView = UIView(frame: CGRect(origin: view.center, size: CGSize(width: 100.0, height: 100.0))) circleView.layer.cornerRadius = circleView.frame.width / 2.0 circleView.center = view.center circleView.backgroundColor = .green view.addSubview(circleView) circleViewModel = CircleViewModel() // Bind the center point of the CircleView to the centerObservable circleView .rx.observe(CGPoint.self, "center") .bindTo(circleViewModel.centerVariable) .addDisposableTo(disposeBag) // Subscribe to backgroundObservable to get new colors from the ViewModel. circleViewModel.backgroundColorObservable .subscribe(onNext: { [weak self] backgroundColor in UIView.animateWithDuration(0.1) { self?.circleView.backgroundColor = backgroundColor // Try to get complementary color for given background color let viewBackgroundColor = UIColor(complementaryFlatColorOf: backgroundColor) // If it is different that the color if viewBackgroundColor != backgroundColor { // Assign it as a background color of the view // We only want different color to be able to see that circle in a view self?.view.backgroundColor = viewBackgroundColor } } }) .addDisposableTo(disposeBag) let gestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(circleMoved(_:))) circleView.addGestureRecognizer(gestureRecognizer) } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 func setup ( ) { // Add circle view circleView = UIView ( frame : CGRect ( origin : view . center , size : CGSize ( width : 100.0 , height : 100.0 ) ) ) circleView . layer . cornerRadius = circleView . frame . width / 2.0 circleView . center = view . center circleView . backgroundColor = . green view . addSubview ( circleView ) circleViewModel = CircleViewModel ( ) // Bind the center point of the CircleView to the centerObservable circleView . rx . observe ( CGPoint . self , "center" ) . bindTo ( circleViewModel . centerVariable ) . addDisposableTo ( disposeBag ) // Subscribe to backgroundObservable to get new colors from the ViewModel. circleViewModel . backgroundColorObservable . subscribe ( onNext : { [ weak self ] backgroundColor in UIView . animateWithDuration ( 0.1 ) { self ? . circleView . backgroundColor = backgroundColor // Try to get complementary color for given background color let viewBackgroundColor = UIColor ( complementaryFlatColorOf : backgroundColor ) // If it is different that the color if viewBackgroundColor ! = backgroundColor { // Assign it as a background color of the view // We only want different color to be able to see that circle in a view self ? . view . backgroundColor = viewBackgroundColor } } } ) . addDisposableTo ( disposeBag ) let gestureRecognizer = UIPanGestureRecognizer ( target : self , action : #selector(circleMoved(_:))) circleView . addGestureRecognizer ( gestureRecognizer ) }

And you’re done! The whole task of manipulating colors without delegates, notifications and that whole boilerplate code we always use for that type of tasks. The result should be somewhat similar to the one from the beginning of the Example.

Now you can try to customize it! Maybe add binding between center and the ball size? Then try to change its cornerRadius based on its width and height ? It’s really up to you but I think that with Rx that tasks are really delightful.

That’s it for today and as always visit our GitHub for whole project and see you guys next time!

P.S. I try to implement more examples on a weekly basis so subscribe(?) to our RxSwiftExamples repo!

Read more articles about RxSwift

RxSwift by Examples #1 – The Basics

RxSwift by Examples #2 – Observable and the Bind

RxSwift by Examples #3 – Networking

RxSwift by Examples #4 – Multithreading