TableViews are everywhere; for years before the introduction of Collection Views they were one of the fundamental block of every application’s.

Even if now we are able to replace the entire functionality with the combo of UICollectionView  & UICollectionViewFlowLayout  (take a look at “The case for deprecating UITableView) they still play a key role during the everyday work life of any iOS developers.

Frankly I always hated the way in which we prepare and manage contents inside table for several different reasons:

  • tons of boilerplate code to declare data source & delegates. I loved the datasource/delegate pattern, but we can do better and we can do it in Swift, of course.
  • it’s weak: declare cell via identifiers (literals!), cast and finally use them; in our new strong-typed world this is a nightmare we need to avoid.
  • complex tables are a nightmare to prepare and manage; usually you will end up in a world full of if/switch conditions to allocate one cell instead of another, do an action if the you tapped this or another cell and so on. I just want to declare content and do actions.
  • Your view controllers are easy to become full of apparently-non-sense-conditions used to manage the content of your tables.

By Alex Kao (http://alexkao.ninja)

What you will get

In this article you will get a different way to play with  UITableView ; it’s called declarative approach and it allows to simplify the entire workflow to manage the content of the table views.

The following code is the only code you need to populate a table with country flags and manage the tap on each:

Just few lines of code, no delegate, no data source.
Everything is clean and readable and you can manage even complex layouts and operations (add/remove/move sections and rows) with simplicity and cleanliness.
Let’s do it!

The code described in this article refers to my open source library called Flow.
To learn more about Flow and how it can simplify your work go to the official Flow GitHub page.

A declarative approach

NOTE
You may also be interested in this:  “Use Swift to make easy and safe dequeue operation in UITableViewCells”.
It describe a simple and safe way to dequeue UITableViewCell; it was the starting point for this work.

Once your table is generated dynamically you may need to add, remove or show particular cells or sections based upon conditions or events; moreover you need to fire the right actions based upon selected cell: both your numberOfRows , cellAtIndex  and didSelect delegate functions are easy to become a hornet’s nest of if/switch conditions (if tapped cell is that but you are not logged in, but when are logged the index is plus one because you have the other cell… does this remind you of anything?).

A declarative approach is able to solve this and other problems.
My goals are:

  • Cell rendering is a business of the cell. Let the cell do its damn job. If you still using tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: IndexPath)  as root for your rendering its time to stop that. Make your code SOLID.
  • Its time to make a step forward from DataSource/Delegate. Setup the cell’s class, a model and use declarative methods to add/remove or manage rows into the table. No more data source, no more delegate, just plain understandable functions: add row, add section, remove row, move section…
  • Type-safe cell allocation and automatic registration. Describe your cell classes, the model represented and let the library take care of the rest.
  • Functional. Cell configurations is easy; you can observe events and manage the behaviour (onTap, onSelect…) of the cells in a pure functional manner.
  • Support Auto Sizing. Easily supports Auto-Layout and provide a simple mechanism to specify the height of a cell or leave the class decide the best one upon described constraints.
  • Easy support for animations. Like as  performBatchUpdates for  UICollectionView  we want manage automatically the animations related to content change (I don’t want to see what is changed and call the related animation function…it’s not my business)

The Architecture

Our approach uses three basic elements to describe the table and its content:

  • TableManager : this class manage the data of the table, it expose functions to add/remove or move cells and sections. You will allocate an istance of TableManager  for each UITableView : it will be only  dataSource  and delegate of the table itself.
  • Section : it represent a section into the table: it may contains   Rows , optional header /  footer  (as plain  Strings  or custom UITableViewHeaderFooterView subclasses ). You will refer to the function of Section  to add/remove/move rows (cells).
  • Row : it represent the single row (a cell); it’s an open class conforms to  RowProtocol : it defines the  UITableViewCell  class used to render data and the model accepted by the cell: once needed the cell will receive the instance of your model and you will be able to render your data.

Each Row is created with a model instance and a represented cell; each Section is created with Row array, optional header/footer. TableManager is the entry point to manage the content displayed.

That’s all!

The following diagram explain how the architecture works.

Architecture of Flow; the TableManager is responsible to instantiate and manage sections with rows and header/footer. Final result is rendered on UITableView instances.

A real example

Nothing is better than an example to show what this approach can enable.

NOTE
For the sake of brevity the following example describe only the core functionality of the library removing some boilerplate code.
Full working example is available along with the library on my GitHub.

We want to represent three different state of a login screen:

  • Welcome screen (an header, email/password fields, login and forgot credentials buttons)
  • Recover screen (an email fields and a recover button)
  • Loader (an intermediate state where we’ll show a spinning loader)
  • Profile (user profile, details rows and friends list)

Prepare the cells

We can achieve it in a single tableview and keep all the logic of each screen clearly separated.
The first step is to create our cells; below the structure of our table you will seen into the live example.

Each cell must conforms to DeclarativeCell  protocol; it simply define the model type received by the cell at runtime and a configure()  function called every time the cell needs to be updated with fresh data.

This is an example of our CellLoginCredentials  cell:

In detail:

  • the public typealias T = LoginCredentialsModel  defines the model class received by instances of CellLoginCredential  when a dequeued.
  • the public func configure(_ credentials: LoginCredentialsModel, path: IndexPath)  is the method called on cell instances when a new cell is dequeued in table. You have the opportunity to update your UI here.
    In this case we want to reflect the login data into the relative fields.
  • if cell has a fixed height we can define it as class property by overriding public static var defaultHeight: CGFloat?

The next step is to set the reuseIdentifier  of the cell to the name of the class itself.
If your cell is defined in a separate xib you also need to set the name of xib to the name of the class itself.

At this time your cell is ready to be used!

Populate the table

Create content for our table is pretty simple; we need to describe just the UITableViewCell subclass we want to use for a determinate row and configure the behaviour.
For our CellLoginCredential :

here we have just created a new Row for our table which uses:

  • CellLoginCredential as UITableViewCell subclass
  • LoginCredentialsModel as model for our class (and  self.credentials as instance for this row)

The callback { row in ... }  is the ideal place to configure our row instance; we can set actions for each event ( onTap , onSelect , onEdit , onDelete , onShouldHightlight , canMove  and all the others).
In the example above we used onDequeue event to hook UIButton’s actions to our view controller functions; moreover we have set fancy action when cell is tapped.
Pretty simple uh?

Add row to the table

Adding/removing/moving rows and sections from our table is really simple. With declarative approach we just need to call the appropriate functions of our TableManager, then call reloadData() to refresh the table’s UI.

For example we can add append the row above in just one line:

Et voilà, no delegate, no data source, just declare your intention; the row itself manage events (dequeue/tap/highlight…) of the associated cell, while the TableManager is responsible to represent the entire table’s structure.

TableManager also supports update with animation; just add the code to alter the content inside a block like below:

TableManager will generate the appropriate code to animate the table with applied changes (add/remove sections, rows…) without any additional code.

Section objects also support custom view as header and footer in the same manner.

The same approach is repeated for each cell of the table; if you are interested you can check out the real working example here.

This piece of code create the login screen:

 

This is the result:

The complete example you will found into the project explore the detail of the screen and how is possible to switch between the various screen we have listed above.

Use it in your app: welcome to Flow!

Flow is the name of the library which implements the approach you have just seen.

Flow is available on my GitHub repository. You are free to use it in your commercial/non commercial product; I’ll be very happy to hear from you suggestions for future development.

▶︎ Flow GitHub Page

I’m currently working on a something similar for collection views; if you are interested drop me an email.