create account

Creating a reusable UITableViewCell by damage-un

View this thread on: hive.blogpeakd.comecency.com
· @damage-un · (edited)
Creating a reusable UITableViewCell
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, when we see the potential for repeating the same code. But are we doing it in the correct way?

I would like to show this problem on a simple example, based on what I have encountered in practice. Imagine a UITableView cell 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

# The problem


![Writing extendable code.png](https://steemitimages.com/DQmfPP14WQXyTyL68mjT5NEYeUcqRiFWFryLJiaPQiJzU6E/Writing%20extendable%20code.png)
(State showing the image and 4 labels)

![Writing extendable code (1).png](https://steemitimages.com/DQma7RRAGxUanQKNMJY9x5QwNrBRbiY5CK9X2CWKBM8m14s/Writing%20extendable%20code%20(1).png)
(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 we have to populate this cell with different data on every screen. This 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*. 

![Screen Shot 2018-02-06 at 22.12.08.png](https://steemitimages.com/DQmfXm6VUagKPrAeCLsoisVz3kGXxEUnQHAevZyqK6aWWnd/Screen%20Shot%202018-02-06%20at%2022.12.08.png)

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.

    struct User {
        let name: String
        let nickname: String
    }

    struct Pet {
        let name: String
        let owner: String
        let breed: String
        let weight: Int
        let avatar: UIImage?
    }

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. 

    func set(with user: User) {
        leftTitleLabel.text = user.name
        leftSubtitleLabel.text = "Nick: " + user.nickname
        hideImage()
    }

    func set(with pet: Pet) {
        leftTitleLabel.text = pet.name
        leftSubtitleLabel.text = "Owner: " + pet.owner
        
        rightTitleLabel.text = pet.breed
        rightSubtitleLabel.text = pet.weight.description
        
        iconImageView.image = pet.avatar
        showImage()
    }
    
    private func showImage() {
        iconWidthConstraint.constant = 50
        iconLeftConstraint.constant = 8
    }

    private func hideImage() {
        iconWidthConstraint.constant = 0
        iconLeftConstraint.constant = 0
    }

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:

<center> **“open for extension, but closed for modification.”**</center>

 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. Everytime we are changing the *ReusableTableViewCell*, we can introduce a bug that can affect a large part of our app. We also would have to consider every case where this cell is used and try not to break that specific case. This can introduce bugs that are hard to discover.

If the cell will be able to 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 be able to display. The cell itself shouldn't be aware of all those classes and how they must be presented.

## So how do we achive this? 
We will introduce a new protocol called *ReusableTableViewCellDataProvider*. This will be the data source that the table view cell will get the data it needs from. This protocol tells us what the cell can display and that's the only thing this cell will be aware of.

    protocol ReusableTableViewCellDataProvider {
        var leftTitle: String? { get }
        var leftSubtitle: String? { get }
        var rightTitle: String? { get }
        var rightSubtitle: String? { get }
        var image: UIImage? { get }
    }

We will than 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.

    struct UserReusableCellDataProvider: ReusableTableViewCellDataProvider {
        let user: User

        var leftTitle: String? {
            return user.name
        }
    
        var leftSubtitle: String? {
            return "Nick: " + user.nickname
        }
    
        var rightTitle: String? {
            return ""
        }
    
        var rightSubtitle: String? {
            return ""
        }
    
        var image: UIImage? {
            return nil
        }
    }

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.

    func set(with dataProvider: ReusableTableViewCellDataProvider) {
        leftTitleLabel.text = dataProvider.leftTitle
        leftSubtitleLabel.text = dataProvider.leftSubtitle
        
        rightTitleLabel.text = dataProvider.rightTitle
        rightSubtitleLabel.text = dataProvider.rightSubtitle
        
        set(image: dataProvider.image)
    }

With *set(image: UIImage)* function we make sure that we hide the imageView if the data provider doesn't return an image

    private func set(image: UIImage?) {
        guard let image = image else {
            hideImage()
            return
        }
        
        showImage()
        iconImageView.image = image
    }

When dequeuing the cell in tableView cellForRowAt indexPath function of the users screen, we will create an instance of the *UserReusableCellDataProvider* and pass it into the *ReusableTableViewCell*.

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: "ReusableTableViewCell") as? ReusableTableViewCell, indexPath.row < users.count  else {
            return UITableViewCell()
        }
        
        let user = users[indexPath.row]
        let dataProvider = UserReusableCellDataProvider(user: user)
        cell.set(with: dataProvider)
        return cell
    }

If we want to display the Pet data in the same cell, we only need to create a new DataProvider.

    struct PetReusableCellDataProvider: ReusableTableViewCellDataProvider {
        let pet: Pet
    
        var leftTitle: String? {
            return pet.name
        }
    
        var leftSubtitle: String? {
            return "Owner: " + pet.owner
        }
    
        var rightTitle: String? {
            return pet.breed
        }
    
        var rightSubtitle: String? {
            return pet.weight.description
        }
    
        var image: UIImage? {
            return pet.avatar
        }
    }

In the screen with the list of pets, we will deque the cell in the same way, but pass in the *PetReusableCellDataProvider*.

# The end result

<center>![Screen Shot 2018-02-07 at 00.04.45.png](https://steemitimages.com/DQmemGKx2cSAHFi12BBCvs8UJ9X9JzcPnAeH83aoJdFG9Uh/Screen%20Shot%202018-02-07%20at%2000.04.45.png)</center>
<center>**Pets**</center>

<br/>

<center>![Screen Shot 2018-02-07 at 00.04.31.png](https://steemitimages.com/DQmS3G5VA7zDFrNDJ8SmZ2kC8ykBY45PHCrPV9anmynQ5ci/Screen%20Shot%202018-02-07%20at%2000.04.31.png)</center>
<center>**Users**</center>


 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 the example project on [GitHub](https://github.com/damijanracel/ReusableTableViewCellExample)
👍  
properties (23)
authordamage-un
permlinkcreating-a-reusable-uitableviewcell
categoryprogramming
json_metadata{"tags":["programming","education","swift","ios"],"image":["https://steemitimages.com/DQmfPP14WQXyTyL68mjT5NEYeUcqRiFWFryLJiaPQiJzU6E/Writing%20extendable%20code.png","https://steemitimages.com/DQma7RRAGxUanQKNMJY9x5QwNrBRbiY5CK9X2CWKBM8m14s/Writing%20extendable%20code%20(1).png","https://steemitimages.com/DQmfXm6VUagKPrAeCLsoisVz3kGXxEUnQHAevZyqK6aWWnd/Screen%20Shot%202018-02-06%20at%2022.12.08.png","https://steemitimages.com/DQmemGKx2cSAHFi12BBCvs8UJ9X9JzcPnAeH83aoJdFG9Uh/Screen%20Shot%202018-02-07%20at%2000.04.45.png","https://steemitimages.com/DQmS3G5VA7zDFrNDJ8SmZ2kC8ykBY45PHCrPV9anmynQ5ci/Screen%20Shot%202018-02-07%20at%2000.04.31.png"],"links":["https://github.com/damijanracel/ReusableTableViewCellExample"],"app":"steemit/0.1","format":"markdown"}
created2018-02-06 23:50:18
last_update2018-02-07 08:15:18
depth0
children2
last_payout2018-02-13 23:50:18
cashout_time1969-12-31 23:59:59
total_payout_value0.000 HBD
curator_payout_value0.000 HBD
pending_payout_value0.000 HBD
promoted0.000 HBD
body_length9,015
author_reputation9,510,350
root_title"Creating a reusable UITableViewCell"
beneficiaries[]
max_accepted_payout1,000,000.000 HBD
percent_hbd10,000
post_id35,522,739
net_rshares608,662,410
author_curate_reward""
vote details (1)
@steemitboard ·
Congratulations @damage-un! You received a personal award!

<table><tr><td>https://steemitimages.com/70x70/http://steemitboard.com/@damage-un/birthday1.png</td><td>Happy Birthday! - You are on the Steem blockchain for 1 year!</td></tr></table>

<sub>_[Click here to view your Board](https://steemitboard.com/@damage-un)_</sub>


> Support [SteemitBoard's project](https://steemit.com/@steemitboard)! **[Vote for its witness](https://v2.steemconnect.com/sign/account-witness-vote?witness=steemitboard&approve=1)** and **get one more award**!
properties (22)
authorsteemitboard
permlinksteemitboard-notify-damage-un-20190128t010209000z
categoryprogramming
json_metadata{"image":["https://steemitboard.com/img/notify.png"]}
created2019-01-28 01:02:09
last_update2019-01-28 01:02:09
depth1
children0
last_payout2019-02-04 01:02:09
cashout_time1969-12-31 23:59:59
total_payout_value0.000 HBD
curator_payout_value0.000 HBD
pending_payout_value0.000 HBD
promoted0.000 HBD
body_length540
author_reputation38,975,615,169,260
root_title"Creating a reusable UITableViewCell"
beneficiaries[]
max_accepted_payout1,000,000.000 HBD
percent_hbd10,000
post_id79,036,854
net_rshares0
@steemitboard ·
Congratulations @damage-un! You received a personal award!

<table><tr><td>https://steemitimages.com/70x70/http://steemitboard.com/@damage-un/birthday2.png</td><td>Happy Birthday! - You are on the Steem blockchain for 2 years!</td></tr></table>

<sub>_You can view [your badges on your Steem Board](https://steemitboard.com/@damage-un) and compare to others on the [Steem Ranking](https://steemitboard.com/ranking/index.php?name=damage-un)_</sub>


###### [Vote for @Steemitboard as a witness](https://v2.steemconnect.com/sign/account-witness-vote?witness=steemitboard&approve=1) to get one more award and increased upvotes!
properties (22)
authorsteemitboard
permlinksteemitboard-notify-damage-un-20200128t004423000z
categoryprogramming
json_metadata{"image":["https://steemitboard.com/img/notify.png"]}
created2020-01-28 00:44:24
last_update2020-01-28 00:44:24
depth1
children0
last_payout2020-02-04 00:44:24
cashout_time1969-12-31 23:59:59
total_payout_value0.000 HBD
curator_payout_value0.000 HBD
pending_payout_value0.000 HBD
promoted0.000 HBD
body_length624
author_reputation38,975,615,169,260
root_title"Creating a reusable UITableViewCell"
beneficiaries[]
max_accepted_payout1,000,000.000 HBD
percent_hbd10,000
post_id94,834,469
net_rshares0