Configuring multiple cells with generics in Swift
![Profile photo of Igor Nemenonok](/_next/image?url=https%3A%2F%2Fchililabs.io%2Fcms%2Fuploads%2FIgor_8c80c27526.jpeg&w=3840&q=75)
![Wall shelf with mugs](/_next/image?url=https%3A%2F%2Fchililabs.io%2Fcms%2Fuploads%2FMultiple_cells_Mobile_75bc78eff6.jpg&w=3840&q=75)
![Wall shelf with three mugs](/_next/image?url=https%3A%2F%2Fchililabs.io%2Fcms%2Fuploads%2FMultiple_cells_7937ccc7c9.webp&w=3840&q=75)
iOS Developers spend most of their development time dealing with UITableView and UICollectionView. It’s quite straightforward when you need to display a list with the same data type, e.g., list of users. But what if you need to present a bunch of different cells in one table view? That can lead to a real mess!
Let’s imagine the following case. We need to display a feed with a different type of cells; it can be UserCell, CommentCell, ImageCell, etc.
The most obvious solution is to make if statements in method tableView(_:cellForRowAt:) and it can look as follows.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let item = viewModel.items[indexPath.row] if let user = item as? User { let cell = tableView.dequeueReusableCell(withIdentifier: "UserCell", for: indexPath) as! UserCell cell.configure(user: user) return cell } else if let message = item as? String { let cell = tableView.dequeueReusableCell(withIdentifier: "MessageCell", for: indexPath) as! MessageCell cell.configure(message: message) return cell } else if let imageUrl = item as? URL { let cell = tableView.dequeueReusableCell(withIdentifier: "ImageCell", for: indexPath) as! ImageCell cell.configure(url: imageUrl) return cell } return UITableViewCell() }
ViewModel:
class TableViewModel { let items: [Any] = [ User(name: "John Smith", imageName: "user3"), "Hi, this is a message text. Tra la la. Tra la la.", Bundle.main.url(forResource: "beach@2x", withExtension: "jpg")!, User(name: "Jessica Wood", imageName: "user2"), "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." ] }
This code has several issues:
- If statements. It can become huge and not readable as number of cells grows.
- Type casting ( as! ). Casts are a code smell.
- For each data type we can have only one cell.
- Boilerplate code.
Let’s make this code simpler, clearer and more elegant.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let item = viewModel.items[indexPath.row] let cell = tableView.dequeueReusableCell(withIdentifier: type(of: item).reuseId)! item.configure(cell: cell) return cell }
Advantages:
- No if statements.
- No typecasting.
- No boilerplate code.
- You don’t need to change the code as number of cells grows.
- Reusable code.
But how to achieve this result? It’s when generic programming comes into play!
Let’s do i <T> with generics
Generic code enables you to write flexible, reusable functions and types that can work with any type, subject to requirements that you define. You can write code that avoids duplication and expresses its intent in a clear, abstracted manner.
Generic programming is an excellent way to avoid boilerplate code and helps to define errors during compilation time.
Let’s make a first protocol that each cell should conform to.
protocol ConfigurableCell { associatedtype DataType func configure(data: DataType) } //example of UserCell class UserCell: UITableViewCell, ConfigurableCell { @IBOutlet weak var avatarView: UIImageView! @IBOutlet weak var userNameLabel: UILabel! func configure(data user: User) { avatarView.image = UIImage(named: user.imageName) userNameLabel.text = user.name } }
Now we can create a generic cell configurator that will configure our table cells.
protocol CellConfigurator { static var reuseId: String { get } func configure(cell: UIView) } class TableCellConfigurator<CellType: ConfigurableCell, DataType>: CellConfigurator where CellType.DataType == DataType, CellType: UITableViewCell { static var reuseId: String { return String(describing: CellType.self) } let item: DataType init(item: DataType) { self.item = item } func configure(cell: UIView) { (cell as! CellType).configure(data: item) } }
CellConfigurator - we need this protocol as we can’s hold generic classes in array without defining data types.
TableCellConfigurator - generic class with type constraints for CellType and DataType. Cell can be configured only if DataType of ConfigurableCell equals to DataType used in TableCellConfigurator.
Few adjustments should be made in our ViewModel.
typealias UserCellConfigurator = TableCellConfigurator<UserCell, User> typealias MessageCellConfigurator = TableCellConfigurator<MessageCell, String> typealias ImageCellConfigurator = TableCellConfigurator<ImageCell, URL> class TableViewModel { let items: [CellConfigurator] = [ UserCellConfigurator(item: User(name: "John Smith", imageName: "user3")), MessageCellConfigurator(item: "Hi, this is a message text. Tra la la. Tra la la."), ImageCellConfigurator(item: Bundle.main.url(forResource: "beach@2x", withExtension: "jpg")!), UserCellConfigurator(item: User(name: "Jessica Wood", imageName: "user2")), MessageCellConfigurator(item: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."), ] }
That’s it!
You can add new cells easily without a need to edit your ViewController’s code.
Let’s add a WarningCell to our table view.
- Conform to ConfigurableCell protocol.
- Add TableCellConfigurator for that cell in ViewModel’s class.
class WarningCell: UITableViewCell, ConfigurableCell { @IBOutlet weak var messageLabel: UILabel! func configure(data message: String) { messageLabel.text = message } } //cell configurator for WarningCell TableCellConfigurator<WarningCell, String>(item: "This is a serious warning!")
I hope this solution will help you in your projects.
Example project is available on GitHub.
Thanks for reading!
Receive a new Mobile development related stories first. — Hit that follow button
Twitter: @ ChiliLabs
Let’s build products together!
Contact us