Binding mutable arrays with ReactiveCocoa

A few months ago I wrote a wrote about a utility class that allows you to bind ReactiveCocoa view models to table views. With this binding helper, each item in the view model array is automatically bound to a cell. The binding helper also automatically updates the table view if the array property on the view model is updated.

Since publishing this binding helper, I’ve had number of people ask me how to handle view model properties which are mutable arrays. This presents a bit of a problem; you can certainly use the binding helper with NSMutableArray properties, however, there is no way to observe an array for changes, making it impossible to know when to update the UI.

In this update, I’ve introduced a new class, CEObservableMutableArray, a mutable array that supports observers. When used as part of a view model, the binding helper is able to automatically update the UI when items are added / removed / inserted etc …

A quick example

This example shows how the binding helper can be used to render a dynamic list of stock prices. Every second the list is randomly updated, with prices changing, new items being added and some being removed or updated.

Here’s the example in action:

The view model for this application is very simple, each item within the list is backed by the following view model:

interface QuoteViewModel : NSObject

property (strong, nonatomic) NSString *symbol;

property (strong, nonatomic) NSNumber *price;

end

The ‘top level’ view model contains an array of these items as follows:

interface QuoteListViewModel : NSObject

property (nonatomic, strong) CEObservableMutableArray *quotes;

end

Notice that the quotes property uses the CEObservableMutableArray type.

The cell, which has its UI defined in a nib, adopts the CEReactiveView protocol:

interface QuoteTableViewCell : UITableViewCell<CEReactiveView>

end

The implementation of this protocol is used to bind each QuoteViewModel to their respective cell:

implementation QuoteTableViewCell 

- (void)bindViewModel:(id)viewModel {
  QuoteViewModel *quoteViewModel = (QuoteViewModel *)viewModel;
  
  self.symbolLabel.text = quoteViewModel.symbol;
  
  // bind the price property, converting it from a number to a string
  [[RACObserve(quoteViewModel, price)
    takeUntil:self.rac_prepareForReuseSignal]
    subscribeNext:^(NSNumber *x) {
      self.priceLabel.text = [numberFormater stringFromNumber:x];
    }];
}

end

The symbol property of the view model is constant and the view just takes the initial state. The price property is dynamic, as a result RACObserve is used to observe changes to the property (via KVO). Because cells can be recycled, we want to stop observing changes to the associated view model when recycling occurs. The simplest way to achieve this is to use the ReactiveCocoa takeUntil operation to ‘terminate’ the signal at the point of recycling.

Finally, the view controller that renders the quotes list is bound to the QuoteListViewModel using the binding helper

implementation QuoteListViewController

- (void)viewDidLoad {
  [super viewDidLoad];
  
  // create the view model
  _viewModel = [QuoteListViewModel new];

  // bind the table view to the list of quotes
  UINib *nib = [UINib nibWithNibName:"QuoteTableViewCell" bundle:nil];
  [CETableViewBindingHelper
      bindingHelperForTableView:self.quotesTableView                
                   sourceSignal:RACObserve(_viewModel, quotes)
               selectionCommand:nil
                   templateCell:nib];
}
end 

The binding helper takes care of informing the table view when items are added / removed / replaced, triggering the required animations.

Summary

If you are using ReactiveCocoa for MVVM hopefully you will find this a useful little addition to your toolbox.

The binding helper code and the example illustrated above are available on GitHub.

Regards, Colin E.

MORE BY COLIN

blog comments powered by Disqus