Frequently, in our work as iOS developers, we have to present some kind of data in a table view or in a collection view. There are many ways to customise the standard Apple components by implementing the their datasource and delegate methods. Most of the time, our task comes down to implementing the required methods for how many rows the table/collection view has and how should each row in that view look like. As a datasource, we usually have an array of model objects. The length of that array determines the first requirement (number of rows) and the data in each entry determines what will be presented in each row.
However, sometimes we have to implement more advanced table views, where some combination of cells might be hidden in one state and displayed in others. In this post, we will see how we can implement this in a more elegant manner, with a very simple, but powerful trick.
Let’s say we have a requirement where we need to display information about a book. The user interface for our simple app should look something like this.
Implementing this should not be a big of a problem for any iOS developer. However, one subtle requirement changes it all – some of the info for the book might be missing. In that case, that particular row of the book should not be displayed in the table view.
The book info is stored in a simple struct, with the following information.
As you can see, the subtitle, the author’s location, the publisher, its logo and the image of the book might be missing. Maybe one of them is missing, maybe all of them or maybe none. We don’t have that information, it depends on some data we get from a server.
Let’s see how we can implement this with our traditional approach when implementing table views.
We take every index path row and we check its value. If it’s zero, then we know we have to present the cell which displays the title. If it’s the first row, we check whether we have a subtitle. If we do, then we present the subtitle cell, otherwise we display the author cell. Not that cool, but we can still manage this. Now we have the second row. Here, we first need to check whether we had a subtitle in the previous row, so we know whether we should display the author cell or the one after (the publisher). We will have few extra conditions.
The further we go with the rows, the more complicated this code becomes. We should always keep track whether we had a subtitle cell, an author location cell, a publisher cell etc. Let’s do some simple math – in our simple Book struct, we have 5 optional values and each one of them can either have a value or not. This means that there are 32 possible combinations of having a value or not, that we should handle in our code.
That’s really hard to code and impossible to maintain. And if we have even more optional values, that would be even more complex.
And imagine one day that the product manager comes to you and says “Let’s put the book’s publishing year in the fourth row and the book’s number of pages in the seventh row”. As you’ve probably guessed it, adding new elements is pretty hard with this approach.
Also, think about the number of rows in the table view. You will need to have the same crazy amount of “if” statements, which will return the number of rows for each state.
Descriptor arrays to the rescue
Simplicity is the ultimate sophistication – Leonardo da Vinci.
There has got to be a better way. Code should be simple and understandable in order for it to be beautiful. Let’s see how we can fix this with one small change – changing the datasource for the table view methods.
We will first create an enumeration called Descriptor, which will define the different types of cells our table view supports.
The raw values of the enumeration will correspond to the reuse identifiers of our table view cells. Next, we will define a descriptor array, which will contain elements of this enumeration type. The array will be filled with data, based on whether there’s value for that property in the book model.
This array can have minimum 2 elements (if only the mandatory title and author are present), up to the total of five possible types of cells.
Now, let’s see how we can use this array in our table view methods.
No single “if” was found in this code! The descriptor array now determines the size of the table view. In the cellForRowAtIndexPath method, we take the descriptor and use it to dequeue the cell. Then, we abstract away the configuration of the cell in a newly created class, called CellConfigurator. Let’s see the code for this class.
Based on the value of the enumeration, we are configuring and returning the cell. For example, for the author cell, we will have the following.
Something similar needs to be done for the other types of cells as well. And basically that’s everything we needed to do in order to have flexible table view cells. Now, let’s create one book and test this.
If we run the app now, we will have something like this.
Now, try putting any of the optional values of the book to nil and re-run the app. The UI will adjust itself accordingly, by removing those cells.
Adding new types of cells is also pretty simple. You will just need to update the Descriptor enum and the loadDescriptorArray method, and possibly add another type of cell configuration in the CellConfigurator. You will not change the existing code, but just implement the new functionality. Now, you can stand firm and tell the product manager that adding the book’s publishing year in the fourth row and the book’s number of pages in the seventh row is a piece of cake.
Another benefit is that your view controllers will be pretty small, even if you are using the MVC pattern.
What do you think about descriptor arrays? Have you used them already in your apps? Do you know some other approaches that you want to share? Give me your thoughts in the comments. You can find the source code for this project here.