An Introduction to Reactive Data Display Manager

Alexander Kravchenkov
Surf
Published in
5 min readFeb 12, 2020

This is the first part in a series of articles about the ReactiveDataDisplayManager (RDDM) library. We’ll look at frequent issues you encounter working with “regular” tables and give an overview of RDDM.

1st issue. UITableViewDataSource

First off, let’s forget about assigning responsibilities, reusing and other fancy words. Here’s what regular work with tables looks like:

class ViewController: UIViewController {}
extension ViewController: UITableViewDelegate {
}
extension ViewController: UITableViewDataSource {
}

Let’s take the most common option as an example. What do we need to implement? That’s right, we generally implement three UITableViewDataSource methods:

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Intfunc numberOfSections(in tableView: UITableView) -> Intfunc tableView(_ tableView: UITableView, cellForRowAt indexPath: indexPath)

Don’t think about the auxiliary methods (numberOfSection etc.) right now. Let’s look at the most exciting one, func tableView(tableView: UITableView, indexPath: indexPath)

Let’s say we want to fill the table with product descriptions. The method will look like this:

Great, that seems easy enough. Now, let’s say we have several cell types, let it be three:

  • Products
  • List of promotional programs
  • Advertising

To make it simple, let’s move cell retrieving to the getCell method:

That’s plenty of code, isn’t it? Imagine that we want to create the settings screen. What should we put there?

  • Header cell with a userpic
  • A set of cells with “deep” navigation
  • Cells containing switches (e.g. enable/disable PIN verification)
  • Cells containing information (phone number, email etc.)
  • Personal offers

Plus, the order is set. It’s going to be one huge method…

And here’s another situation. Here we have an input form. It contains plenty of identical cells, each handles a specific field in the data model. For instance, the cell with phone input form handles Phone.

Sounds easy but there’s a catch. You still have to cover various scenarios because you need to update all the desired fields.

Let’s fantasize some more. Imagine a Backend Driven Design where we get 6 different types of input fields. Depending on the fields’ properties (visibility, input type, validation, default value etc.), cells change so much that you can’t adjust them to the same interface. Even if you decompose the configuration into different methods, it will look like a mess.

Think about what your code will be like if you decide to add or remove cells as you go. We’ll have to track both the consistency of the data stored in the ViewController and the number of cells. It won’t look very pretty.

Disadvantages:

  • You’ll get spaghetti code if there happen to be cells of different types.
  • Processing events from the cells will be problematic.
  • You’ll get ugly code in case you need to change the table state.

2nd issue. MindSet

Still too early for fancy terminology.

Let’s take a look at how the app runs, specifically, how data appear on the screen. We always see this as a consistent process, more or less:

  1. Retrieve data from the network.
  2. Process it.
  3. Display this data on the screen.

But is this really the case? No, it’s not. Here’s what we actually do:

  1. Retrieve data from the network.
  2. Process it.
  3. Save a model inside ViewController.
  4. Something causes the screen to refresh.
  5. Convert saved model into cells.
  6. Data is displayed on the screen.

There are more differences than just number of steps. First, we no longer call data manually, it is displayed automatically. Second, there is a logical gap in the way data is processed: the model is saved and the process finishes. Then something happens and another process begins. Thus, we don’t add elements to the screen explicitly but save them for later (which might have unpleasant consequences).

Now let’s turn to UITableViewDelegate. It contains methods for determining cell height. AutomaticDimension is usually enough but sometimes you have to set the height manually when it comes to animations, headers etc.

In such cases we usually separate cell settings and move the height configuration part to another method.

Disadvantages:

  • We lose evident connection between data processing and its display on the UI level.
  • Cell configuration is broken into multiple parts.

Concept

All these issues appearing on complex screens can be quite a nuisance and trigger heavy tea drinking.

First, you don’t want to implement delegate methods over and over again. The obvious solution would be to create an object that will implement the delegate. Here’s what we will do:

let displayManager = DisplayManager(self.tableView)

Cool. Now we need to make this object work with any cells and at the same time move cell configuration to some other place.

If we move configuration to a separate, we will encapsulate it in one place. Finally, it’s time for fancy words. We can move the data formatting logic here as well (changing date format, string concatenation etc.). We can subscribe to events in a cell using this very object.

This way we get an object with two interfaces:

  1. Interface to generate UITableView instances for DisplayManager
  2. Interface for initialization, subscription and configuration for either Presenter or ViewController

Let’s call this object a generator. The table sees the generator as a cell. For everything else it’s a way to display data on UI and process events.

Since the configuration is now encapsulated by the generator, and the generator is presented as a cell, it solves an array of problems, including ones I listed above.

Implementation

Using such an implementation we can create a default implementation:

Here’s an example of a small-scale generator:

Here we hid both configuration and subscriptions. Now we have a place where we can encapsulate state. Note that you can’t encapsulate state in a cell because it is being reused by the table. We can also change the data in a cell on the go.

Take a look at self.cell = view. We had memorized the cell and from now on we can update the data without reloading it. That’s a handy characteristic.

But I digress. Since any cell can be represented by a generator, we can make the interface of our DisplayManager a bit more stylish.

But there’s more to it. We can insert generators where we need them or delete them.

Inserting a cell next to a specific cell can prove to be pretty useful, especially if we load the data gradually. For example, the user enters their tax payer ID, we load respective information and display it by adding new cells next to this field.

Conclusion

Here’s what working with cells will look like from now on:

Here is another option:

We can monitor the order of adding elements while maintaining the connection between data processing and adding data to UI. To sum up, we use simple code in simple cases and avoid spaghetti code in complicated situations. Using a declarative interface allows us to encapsulate cell configuration with its own tools. This way we can reuse cells with configuration between different screens.

Advantages of using RDDM:

  • Encapsulating cell configuration
  • Reducing code duplication by encapsulating work with a collection in an adapter
  • Selecting an object adapter which encapsulates the logic of working with a collection
  • The code is clear and easy to read
  • The amount of code needed to add a table is reduced
  • Event processing is simplified

You can find source code here.

--

--