A practical MVVM example in Swift – Part 2 (featuring RxSwift)

Datetime:2016-08-23 01:24:36          Topic: MVVM Model           Share

Welcome to part 2 of the practical MVVM example in Swift. If you missed part 1, make sure to check it outhere.

Binding MVVM into something meaningful

We’ve seen the benefits of using an MVVM approach in thefirst part of this tutorial, but we’ve mostly been displaying data. This time, we’ll do some updating as well.

What to use?

On iOS, the first thing that comes to mind is usually Key-Value Observing (KVO). It does a great job, but we have to write a lot of boilerplate code. And we don’t want that, do we?

For this example, I’ve decided to play with an interesting programming paradigm called Functional reactive programming . If you’re not familiar with it, read this great introduction by @andrestaltz . Then, come back, obviously.

There are a few iOS implementations of FRP, two of the most popular ones are ReactiveCocoa and RxSwift .

In my past Objective-C projects, I’ve used ReactiveCocoa, but since RxSwift is a Swift implementation of Rx , we’ll use it for this project. The implementation itself doesn’t really matter that much, what matters this time is to demonstrate how we can use it together with MVVM to write a short but powerful little demo.

Note: I won’t go into too many details of RxSwift since this post will primarily be about MVVM.

Installation of RxSwift

We’ll follow the RxSwift installation guide for CocoaPods. We create a new Podfile:

use_frameworks!
 
def rx_swift
  pod 'RxSwift',    '~> 2.0'
  pod 'RxCocoa',    '~> 2.0'
end
 
target 'MVVM' do
  rx_swift
end
 
target 'MVVMTests' do
  rx_swift
end
 
target 'MVVMUITests' do
 
end

And run pod install then open MVVM . xcworkspace .

Making CarViewModel reactive

Obviously, the first step is to make our CarViewModel reactive. The Car model should stay unchanged, since it’s not communicating with the UI directly, remember? It’s just there to hold data.

Since we’ll be working with signals, observers, and observables, we need to make sure we have a way of cleaning up everything, so we don’t have any resources permanently allocated (and causing memory leaks).

The first thing we’ll do to our CarViewModel is import RxSwift and RxCocoa and add a dispose bag.

import RxSwift
import RxCocoa
 
// class CarViewModel {
// ...
 
  let disposeBag = DisposeBag()
 
// ...
// }

Now, we’ll tweak the properties of the CarViewModel so they’ll be able to observe and react to changes.  BehaviorSubject is perfect for that.

// class CarViewModel {
// ...
 
  var modelText: BehaviorSubject<String>
  var makeText: BehaviorSubject<String>
  var horsepowerText: BehaviorSubject<String>
  var kilowattText: BehaviorSubject<String>
  var titleText: BehaviorSubject<String>
 
// ...
// }

A sharp eye might notice that we’ve added the kilowattText variable which wasn’t here before. It’s added to make our lives a bit easier once we start doing the bindings themselves.

The only thing remaining is the initializer where we previously just assigned the car variable to the CarViewModel object, but this time we’ll have to do a bit more work (which will pay off in the future).

Here’s the full initializer which I’ll explain in chunks further on:

// class CarViewModel {
// ...
 
init(car: Car) {
  self.car = car
  
  // 1
  modelText = BehaviorSubject<String>(value: car.model) // initializing with the current value of car.model
  modelText.subscribeNext { (model) in
    car.model = model                                  // subscribing to changes in modelText which will be reflected in CarViewModel's car
  }.addDisposableTo(disposeBag)
  
  // 2
  makeText = BehaviorSubject<String>(value: car.make)
  makeText.subscribeNext { (make) in
    car.make = make
  }.addDisposableTo(disposeBag)
  
  // 3
  titleText = BehaviorSubject<String>(value: "\(car.make) \(car.model)")
  [makeText, modelText].combineLatest { (carInfo) -> String in
    return "\(carInfo[0]) \(carInfo[1])"
  }.bindTo(titleText).addDisposableTo(disposeBag)
  
  // 4
  horsepowerText = BehaviorSubject(value: "0")
  kilowattText = BehaviorSubject(value: String(car.kilowatts))
  kilowattText.map({ (kilowatts) -> String in
    let kw = Int(kilowatts) ?? 0
    let horsepower = max(Int(round(Double(kw) * CarViewModel.horsepowerPerKilowatt)), 0)
    return "\(horsepower) HP"
  }).bindTo(horsepowerText).addDisposableTo(disposeBag)
 
}
 
// ...
// }

The first two bits are mostly doing the same – being initialized with the current value and registering for changes so the car variable updates respectively.

The third bit ( titleText ) is a bit more trick. It uses the combineLatest function to combine whatever changes the makeText and titleText receive, combine them into one string and assign them to titleText .

The fourth bit uses a feature called map ( ) to calculate horsepower from kilowatts. It tries to parse a number from whatever string the kilowattText subject holds and binds the calculated and decorated horsepower string to horsepowerText .

As you’ve probably noticed, we’re explicitly adding all signals to our disposeBag for the reason stated above.

Making the project compile again

We introduced a totally new concept to our app so if we try to run it, we’ll get a build error in TableViewController on these two lines:

// override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// ...
 
  cell.textLabel?.text = carViewModel.titleText
  cell.detailTextLabel?.text = carViewModel.horsepowerText
 
// ...
// }

This is because our titleText and horsepowerText are not normal Strings anymore. The good news is that we’ll ditch our TableViewController completely and start off with a new (much shorter) View Controller that’ll contain some RX magic.

ReactiveTableViewController

First, delete the existing TableViewController . swift and the View Controller that’s in our Main . storyboard .

Next, let’s add a new (basic UIViewController ) and name it ReactiveTableViewController .

Since there’s nothing on Main . storyboard , we have to add the corresponding View Controller and set its class to ReactiveTableViewController :

Note: We’ll be using navigation in the example, so I’ll embed our view controller in a Navigation Controller by selecting it (like on the screenshot) and going to Editor > Embed In > Navigation Controller .

We based our RactiveTableViewController on a UIViewController , so we have to add a table view ourselves. I configured it fullscreen like this:

We have our swift class and our view controller on the storyboard. The dance wouldn’t be complete without linking them together with an IBOutlet. We know how to do that, right? (Hint: View > Assistant Editor > Show Assistant Editor , then Ctrl+drag from the table view to the start of the ReactiveTableViewController  class).

Custom table view cell

As one of the readers of Part 1 noted, we didn’t have a specific Car View in that example, so we’ll add one now!

Let’s drag a new UITableViewCell in the newly created table view and add one image and two labels to it:

PS: If you’re comfortable with Auto Layout, you should definitely check out my Auto Layout Fundamentals for iOS !

We’ll also need a new class for our custom cell. Let’s name it CarTableViewCell :

Next up is setting the class of our prototype cell to CarTableViewCell so that we can do some linking from the storyboard to our CarTableViewCell class:

We know some reactiveness is going to happen, so let’s also add a DisposeBag and one more important thing – a variable holding the CarViewModel object:

import RxSwift // Don't forget this!
 
// class CarTableViewCell: UITableViewCell {
// ...
  
  let disposeBag = DisposeBag()
  var carViewModel: CarViewModel?
 
// ...
// }

The base is set up, so let’s finally look at how we can use RxSwift to make our ReactiveTableViewController well … reactive!

Most boilerplate-less table view controller you’ll ever see

To kick things off in our ReactiveTableViewController , we’ll have to add two things – the DisposeBag for reactiveness and obviously the car data source:

import RxSwift // Don't forget this!
 
// class ReactiveTableViewController: UIViewController {
// ...
  
  var cars: Variable<[CarViewModel]> = Variable((UIApplication.sharedApplication().delegate as! AppDelegate).cars)
  let disposeBag = DisposeBag()
 
// ...
// }

To define the cell size, we add this line in viewDidLoad ( ) :

// class ReactiveTableViewController: UIViewController {
// ...
 
  override func viewDidLoad() {
    super.viewDidLoad()
 
    tableView.estimatedRowHeight = 80 // cells will be 80pt high
 }
 
// ...
// }

Where’s the reactiveness you might ask?! Check this out (still in viewDidLoad ):

// class ReactiveTableViewController: UIViewController {
// ...
//   override func viewDidLoad() {
//     ..
 
    cars.asObservable().bindTo(tableView.rx_itemsWithCellIdentifier("CarCell", cellType: CarTableViewCell.self)) { (index, carViewModel: CarViewModel, cell) in
      cell.carViewModel = carViewModel
    }.addDisposableTo(disposeBag)
 
    // Is this the real life? Is this just fantasy?
 
//     ..
//   }
// ...
// }

If we try to run the app, it won’t work yet, but it should compile if you try ⌘+B .

Half-time recap

To recap what we’ve done so far:

  • Added RxSwift to our project
  • Tweaked  CarViewModel  so it now holds Rx-friendly variables
  • Got rid of the old TableViewController
  • Replaced it with a ReactiveTableViewController  which is a subclass of UIViewController
  • Add a CarTableViewCell  (which is a subclass of UITableViewCell )
  • Linked everything up from the storyboard to code
  • Wrote the most boilerplate-less table view controller ever

The only missing piece of the display-puzzle now is setting up the CarTableViewCell to display actual data in the two labels and the image view.

Reactive table view cell

Our (cleaned up) CarTableViewCell currently looks like this:

import UIKit
import RxSwift
 
class CarTableViewCell:UITableViewCell {
 
  @IBOutletweak var carPhotoView: UIImageView!
  @IBOutletweak var carTitleLabel: UILabel!
  @IBOutletweak var carPowerLabel: UILabel!
 
  let disposeBag = DisposeBag()
  var carViewModel: CarViewModel?
 
}

When we assign the carViewModel to a cell, nothing happens. Let’s change that:

// class CarTableViewCell: UITableViewCell {
// ...
 
  var carViewModel: CarViewModel? {
    didSet {
      guardlet cvm = carViewModel else {
        return
      }
      
      cvm.titleText.bindTo(carTitleLabel.rx_text).addDisposableTo(self.disposeBag)
      cvm.horsepowerText.bindTo(carPowerLabel.rx_text).addDisposableTo(self.disposeBag)
    }
  }
 
// ...
// }

If you remember what we did in CarViewModel – we connected titleText and horsepowerText to the Car model so every time they change, those properties will fire off a signal.

In the two interesting lines above, we basically bound those observables to the carTitleLabel and carPowerLabel labels in our cell. And we’re already familiar with the disposable bags, right?

We’re not 100% done with the cell, but we can safely run the project and see that our three cars are properly displayed in the table view. Hooray!

Obviously, we’re missing car photos. A bit boring without them, right?

In our first example, we used the good ol’ async data fetching using NSData ( contentsOfUrl : ) . But we’re reactive now; we can do much better!

To enable photo loading, we can use NSURLSession with RxSwift support:

// var carViewModel: CarViewModel? {
// ...
 
  guardlet photoURL = cvm.photoURL else {
    return
  }
 
  NSURLSession.sharedSession().rx_data(NSURLRequest(URL: photoURL)).subscribeNext({ (data) in
    dispatch_async(dispatch_get_main_queue(), {
      self.carPhotoView.image = UIImage(data: data)
      self.carPhotoView.setNeedsLayout()
    })
  }).addDisposableTo(self.disposeBag)
 
// ...
// }

⌘+Rand here we go! Images are properly loaded, and we’re happy programmers!

What about tests?

You’re right! UI tests should still pass, since the UI looks similar and holds all the information it held in part 1.

Unit tests will not even build because we changed the CarViewModel to hold BehaviorObjects , not Strings .

To make unit tests pass again, we just have to call value ( ) on all BehaviourObjects which displays the current value it holds:

func testCarViewModelWithFerrariF12() {
  let ferrariF12 = Car(model: "F12", make: "Ferrari", kilowatts: 544, photoURL: "http://auto.ferrari.com/en_EN/wp-content/uploads/sites/5/2013/07/Ferrari-F12berlinetta.jpg")
  let ferrariViewModel = CarViewModel(car: ferrariF12)
  XCTAssertEqual(try! ferrariViewModel.modelText.value(), "F12")
  XCTAssertEqual(try! ferrariViewModel.makeText.value(), "Ferrari")
  XCTAssertEqual(try! ferrariViewModel.horsepowerText.value(), "730 HP")
  XCTAssertEqual(ferrariViewModel.photoURL, NSURL(string: ferrariF12.photoURL))
  XCTAssertEqual(try! ferrariViewModel.titleText.value(), "Ferrari F12")
}

Run the whole test suite with ⌘+U and everything should pass!

Let the updating begin!

Up until now, we’ve basically just repeated what we did in part 1 – set up the MVVM architecture and display data from the cars data source. But it’s much cleaner now. And the best part is still yet to come.

At the begining of the post, I’ve mentioned we’re going to be updating data. To do that, we’ll add a new view controller with three text fields that will let us update the data and see the benefits of using a MVVM approach.

Before that, we should add at least one test to verify that our CarViewModel is updating and calculating everything as we predict.

func testAdjustingKilowattsAdjustsHorsepower() {
  let someCar = Car(model: "Random", make: "Car", kilowatts: 100, photoURL: "http://candycode.io")
  let someCarViewModel = CarViewModel(car: someCar)
  XCTAssertEqual(try! someCarViewModel.horsepowerText.value(), "134 HP")
  someCarViewModel.kilowattText.onNext("200")
  XCTAssertEqual(try! someCarViewModel.horsepowerText.value(), "268 HP")
  // Minimum power is 0
  someCarViewModel.kilowattText.onNext("-20")
  XCTAssertEqual(try! someCarViewModel.horsepowerText.value(), "0 HP")
}

In this new test we’re verifying that changing the horsepowerText properly calculates kilowatts and assigns the string to the kilowattText variable. Because of our smart map ( ) in the CarViewModel initializer, the values are calculated properly and it even handles a negative kilowatt value which results in 0 HP .

Now that we’re confident our View Model works properly, we can continue with creating a view controller that will let us update data in our cars array.

CarViewController

Time for some UI! We’ll start by creating a new UIViewController subclass called CarViewController .

Then, in Main . storyboard , we’ll add a view controller containing 3 text fields. The view controller will be connected to our ReactiveTableViewController with a segue called showCar .

To be able to link those text fields, we know what we have to do, right? Set the class of the view controller to CarViewController and Ctrl+drag them from the storyboard to our class using the Assistant Editor in Xcode. The result should look like this:

We also need a variable that will hold the CarViewModel and a dispose bag for RxSwift:

import RxSwift // Don't forget this
 
// class CarViewController: UIViewController {
// ...
 
  var carViewModel: CarViewModel?
  let disposeBag = DisposeBag()
 
// ...
// }

Now let’s switch to the viewDidLoad function and write the code that will display and update data. If you’ve done this in the past without Functional reactive programming and MVVM, you’ll know that we’d need a text field delegate, then check which field the user is editing, map the fields to the Car properties or have some sort of switch or if statements that will update the proper field.

But not with RxSwift and MVVM!

Let’s start (note that we’re in the viewDidLoad function)

override func viewDidLoad() {
  super.viewDidLoad()
  
  // We're using guard to make sure we've got a carViewModel assigned
  guardlet carViewModel = carViewModel else {
    return
  }  
 
  // Assigning carViewModel's properties to our three text fields
  carViewModel.makeText.bindTo(makeField.rx_text).addDisposableTo(disposeBag)
  carViewModel.modelText.bindTo(modelField.rx_text).addDisposableTo(disposeBag)
  carViewModel.kilowattText.bindTo(kilowattField.rx_text).addDisposableTo(disposeBag)
 
  // Binding whatever the input is in our three text fields to our carViewModel's properties
  makeField.rx_text.bindTo(carViewModel.makeText).addDisposableTo(disposeBag)
  modelField.rx_text.bindTo(carViewModel.modelText).addDisposableTo(disposeBag)
  kilowattField.rx_text.filter({ (string) -> Bool in
  // Validate we are only passing integer or empty strings (which result in 0 HP)
  return Int(string) != nil || string.isEmpty
  }).bindTo(carViewModel.kilowattText).addDisposableTo(disposeBag)
 
  // Assigning the titleText to our View Controller title
  carViewModel.titleText.subscribeNext { (title) in
    self.navigationItem.title = title
  }.addDisposableTo(disposeBag)
}

This might look complicated, but the comments should guide you through. There practically isn’t anything new that we haven’t seen in the CarViewModel ’s initializer. Now compare these short 15 lines of code (including comments) to whatever we were used to writing with the UITextFieldDelegates , checking which text field updates which car property, etc … It’s a bliss!

Ok, so to test if we’ve wired the fields up properly, we have to actually perform our showCar segue and assign the carViewModel . We’ll use RxSwift for this as well and keep our ReactiveTableViewController nice and clean. Let’s open the file and navigate to the end of viewDidLoad and add this:

// class ReactiveTableViewController: UIViewController {
// ...
//   override func viewDidLoad() {
//   ...
 
      tableView.rx_itemSelected.subscribeNext { (indexPath) in
        self.performSegueWithIdentifier("showCar", sender: indexPath)
        self.tableView.deselectRowAtIndexPath(indexPath, animated: true)
      }.addDisposableTo(disposeBag)
 
//   ...
//   }
// ...
// }

What does this do? If we look at the function name, it’s pretty self-explanatory. Whenever an item is selected ( rxItemSelected ), our subscribeNext will get fired, and we’ll perform the showCar segue and deselect the row (we shouldn’t forget this!).

If we try to run the project at this point, the new CarViewController should be presented, but the text fields won’t hold any data because the guard is returning out of the viewDidLoad function. We’re still not passing the selected carViewModel to our new VC. Here’s how to do that:

// class ReactiveTableViewController: UIViewController {
// ...
 
  override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    guardlet indexPath = senderas? NSIndexPath, carVC = segue.destinationViewController as? CarViewController else {
      return
    }
    carVC.carViewModel = cars.value[indexPath.row]
  }
 
// ...
// }

A guard to make sure we have the correct destination view controller and that the sender object is an indexPath. It could also be the carViewModel itself – both approaches are fine.

Next, we set the carViewModel on our destination VC and run the project.

Considering how few lines of code we’ve written, the result is stunning. It’s not perfect – we’d still have to put some checks in place, so the user does not input crazy values in kilowatt field and such – but it’s still a great demonstration of the power that MVVM combined with a binding mechanism has.

The final result:

As always, you can see the full project on GitHub or download the latest zipped version directly. If you liked this post or have any suggestions, make sure you leave a comment below and subscribe to my newsletter!

Thanks for reading!





About List