When we code, we always try not to repeat ourselves. If we have multiple implementations of the same functionality, it will cause problems for us somewhere down the line. For this reason, we would usually reuse a component whenever we see the potential for repeating the same code. But are we doing it correctly?
I would like to demonstrate this problem in a simple example based on what I have encountered in practice. Imagine a UITableViewCell implementation of a cell that is being used more than once in the app. In my example, the cell looks the same, but it is displaying different data. It also might be showing or hiding some components.
(State showing the image and four labels)
(State showing only two of the labels)
We have title and subtitles on the left and right side and an image view. We will show the same cell in many screens in the app. Sometimes we have to hide some of the components and populate this cell with different data on every screen. In this case, the cell must display the user data on one screen and the data for a pet in another screen.
So, we create one cell in order to reuse it everywhere we need it. We create a view in the interface builder and connect it to the Swift class: ReusableTableViewCell.
Here we have two models, User, and Pet, that we want to display in this cell. Since the user doesn’t have a picture, we should hide the image view when displaying a user.
One way of implementing this would be to add two functions to the ReusableTableViewCell, set(with user: User) and set(with pet: Pet). In those two functions, we would set the labels and toggle the visibility of the image view. We would hide the image view when displaying the user data and show the image view when displaying the pet data.
I would argue that this is not the best way to go about doing something like that. I would like to reference the open-closed principle that says that classes should be:
“open for extension but closed for modification.”
The principle originated with Bertrand Meyer who wrote about it in his book Object-Oriented Software Construction. You can also read more about it in an article from Robert C. Martin, The Open-Closed Principle.
A Different Approach
Our goal is to write the ReusableTableViewCell once, but leave it open for extension for all the existing cases and all the cases that may come up in the future. So, what do we gain from this? We won’t ever have to change the code in the ReusableTableViewCell itself, or at least we won’t have to change it that often. This will help a lot if we have many places in our app that are using this cell. Every time we change the ReusableTableViewCell, we could introduce a bug that can affect a large part of our app. We would also have to consider every case where this cell is used and try not to break that specific case. This can also introduce bugs that are hard to discover.
If the cell can show a lot more models in the future, it will get bloated, since we will have to write a function for every model it will display. The cell itself shouldn’t be aware of all those classes and how they must be presented.
So how do we achieve this?
We will introduce a new protocol called ReusableTableViewCellDataProvider. This will be the data source from which the table view cell will get the data it needs. This protocol tells us what the cell can display, and that’s the only thing this cell will be aware of.
We will then add a UserReusableCellDataProvider that will implement the ReusableTableViewCellDataProvider protocol. It will act as an adapter between the ReusableTableViewCell and the User model. It is the job of the UserReusableCellDataProvider to provide the data that the cell can display.
In the cell itself, we can remove the functions that accept concrete classes for User and Pet since we only need to pass in any object that implements the ReusableTableViewCellDataProvider protocol.
With the set(image: UIImage) function we make sure that we hide the imageView if the data provider doesn’t return an image
When dequeuing the cell in tableView cellForRowAt indexPath function of the user’s screen, we will create an instance of the UserReusableCellDataProvider and pass it into the ReusableTableViewCell.
If we want to display the Pet data in the same cell, we only need to create a new DataProvider.
In the screen with the list of pets, we will dequeue the cell in the same way, but pass in the PetReusableCellDataProvider.
The end result:
As you can see, we have created two ways of displaying data in our cell, but we didn’t have to touch any of the code in the cell itself. If we wanted, we could also add new DataProviders without bloating the cell.
You can find this example project on: [GitHub](https://github.com/damijanracel/ReusableTableViewCellExample)