A Story of MVVM With UITableViews (Part 1)

Bisera
The Startup
Published in
7 min readSep 27, 2020

This is a story of an implementation of MVVM pattern in table view controllers. It’s using UIKit, released a bit late considering that now we have the new best thing in iOS development — SwiftUI, but anyway as it was staying in my drafts folder I wanted to eventually put it out here. And as I was trying out the same sample project with using Combine and SwiftUI might be interesting to compare the different implementation at the end.

In this first part I will cover the good part of the story — how this approach worked perfectly fine for read only data. In the second part you can see how things got pretty complicated when user input was needed.

When starting new project (or in my case redesigning the UI layer) always comes up the question about which architecture and design patterns to be used. And we always think “Oh yes, now we will make it perfect! We will throw away that shitty code and make it the best now!”… too bad that this usually doesn’t come out as expected. Every approach has it’s own good and bad sides, but code can always be improved, it’s never perfect.

And of course, it starts with research.. reading books, articles, best practices, other app examples.. everything is out here. So yes, you will read many things for MVC, MVVM, MVP, Viper, you will compare them and try to find what fits best for your project. After all of this my decision was to go with MVVM for several reasons:

  • It’s avoided writing much additional boilerplate code
  • Easy to make the whole UI testable
  • The app is based on table views for all screens, reusable cells etc

I won’t go into details or comparisons with the other patterns, I would just mention here the main concept of MVVM:

The Model contains the data that needs to be shown, the View (view controller) is responsible for showing the data and the ViewModel processes the data and prepares it in a format that can be displayed on the screen. So the view model will be responsible for all the logic, transforming the model data to data that is easily shown by the view.

What do we get:

  • Lightweight view: All the logic is within the view model, so there is little code in the view controller
  • Testability: The whole app can be tested including the UI with UnitTests

These are the main concepts in this approach with using MVVM with table views:

  • Every view controller has it’s view model
  • Every cell / header / footer or any other view also has it’s view model
  • Every cell view model has extension which tells what type of cell view it needs to use

OK, that is the theory! We have read all these rules, we know it! But it doesn’t always work that great in practice, right? Especially not in huge and complex apps.

So, let’s get to the practical part. I will explain now the whole structure of the sample app and I will mention what are the advantages of this approach.

General setup / base classes

Let’s go through the base classes setup:

BaseTableDataSource — this is the data source which our view controllers will use. It knows how to construct a table just by having a list containing all the section view models (tableData).

TableSectionVM — just a structure identifying every section. It has a list of view models identifying the cells for that section. It can also contain header and footer view models which are optional.

CellVM — this for now is just an empty protocol which all the cell view models should conform to so they can be properly used for displaying the corresponding cells.

IdentifiableCellVM — protocol which the cell view models extensions need to implement so the data source knows which view it should use for the given view model. It identifies the cell view type.

Regarding the cells setup the main concepts are the following:

  • Every cell view model contains only the parameters needed for the cell view.
  • Every cell view model conforms to CellVM. Also it’s extension conforms to IdentifiableCellVM which is used for finding the view to be shown.
  • Every cell conforms to CellView and implements a setup method with the view model as parameter. In the setup method the views are filled with the corresponding values contained in the view model.

The setup for the headers and footers is also pretty similar.

BaseTableViewDataSource — knows how to reuse headers/ footers and cells for the tables. Contains an object conforming to TableSource which returns the cell types and the cell view models needed for constructing the table. Sets up those views with the corresponding view model. Handles the delegate methods from UITableViewDataSource.

This class will be the data source used by all our table view controllers, so we don’t have to always implement the same delegate methods.

BaseTableVM — all view models that are used by the view controller must conform to this protocol. Contains the basic needed properties for constructing the screen. BaseTableVMDelegate is the protocol that the view controllers will conform to, used from the view model so it can notify the view when it should be updated.

BaseTableVC — all view controllers should extend from this class. It contains the initial setup and displaying of the table view data. Also handles the UITableViewDelegate methods. This base view controller is always linked to a generic base table view model which as I mentioned earlier contains the data source view models for display. On viewDidLoad we always have a few setup methods which should be implemented by the child classes.

Places app implementation

OK, that was for the base classes that were implemented for easier development of new views. In the sample project I have implemented a simple feature about places overview. This is how it looks like:

Places overview view controller

When developing this feature we start with our PlacesOverviewVC and PlacesOverviewVM which should extend the base classes / protocols we already mentioned. Next we should define which cells we will be using, in this example we have two types of cells. For the cells and headers, as mentioned, we also have view model and view class.

The main view controller seems pretty clean! There is really little code. Imagine, a view controller with like only 20 lines of code! It only registers the cell views, initializes the view model and handles button action.

All the important work is left to the view model. In PlacesOverviewVM you will notice that there is more code, but it makes sense as the whole logic for the list shown on screen is there. It has methods for grouping by category and grouping by country. Also when the grouping type has been changed it notifies the view to be updated accordingly.

We have two types of cells. PlaceImageCell shows only the title and the image and info whether the place is visited. The other — PlaceCell (when grouped by country) shows additionally info about stars, ratings and type of place.

Overall this works pretty good for showing the list of places in our case. We can group it by different criteria, which is done easily in the view model and we just change the data for the data source correspondingly. It works smooth and fast.

The best thing here is that we could test it all, independently from the UI!

We are able to test everything we show on the screen where is shown some mix of Places and Ratings models. We can write unit tests that will test whether the proper data is shown in all the labels, buttons etc, we can check whether the icons and ratings are correct depending of the place data. This is nice as it brings good code coverage and we know that our views are unit tested. Our code coverage jumped incredibly fast when we rewrote our view controllers using this approach!

Places overview tests

Conclusion

It can be concluded that this was a good approach for applications that only show information with minimal user input.

  • view controllers are small and understandable
  • all the logic is in the view models
  • structured unit tests for the view models, good code coverage

Overall it seems pretty clean and structured.

Regarding using animations with this approach, it can be done but a few modifications are needed because currently the whole tableview is reloaded. In case where we need cells to be shown or hidden with animations we would need to reload only the specific index paths.

Source code can be found here:

Part 2 can be found here:

Thank you for reading! Any suggestions for improvement are welcome!

--

--