Skip to content

Build views, not cells

Posted on:March 18, 2023

Over the years, I’ve come to learn that I rarely want to directly add content to my UITableViewCell and UICollectionViewCells. I’ve landed on creating UIViews instead, which are contained fully within a cell – for the sake of versatility, reusability, and simplicity. iOS 14 introduces a new type: UIContentConfiguration. I’ll also touch on it and why I prefer not to use it.

Benefits of Building UIViews over UITableViewCells

Reusability

One of the main benefits of building UIViews as subviews is reusability. When you build your custom UI elements as subviews, you can reuse them in other parts of your app. Views can be reused within UITableViewCell, UICollectionViewCell, or as part of a larger UIView. By building it as a subview, you can easily add it to any other view or cell without duplicating code.

Better Separation of Concerns

Another benefit of building UIViews as subviews is better separation of concerns. When you build your custom UI elements as subviews, you can separate the view logic from the cell logic. This can make your code more modular and easier to understand. It also makes it easier to test and debug your code.

For example let’s create a cell that contains a single ContactView as its child view:

import UIKit

final class ContactCell: UITableViewCell {
    private let view = ContactView()

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)

        view.translatesAutoresizingMaskIntoConstraints = false
        contentView.addSubview(view)

        NSLayoutConstraint.activate([
            view.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
            view.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
            view.topAnchor.constraint(equalTo: contentView.topAnchor),
            view.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
        ])
    }

    func bind(viewModel: ContactView.ViewModel) {
        view.bind(viewModel: viewModel)
    }

    // MARK: - Unavailable

    @available(*, unavailable)
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

In this view, we only worry about things at the cell level. We could modify the separatorInset, or tackle behaviors and properties specific to the cell — or further insetting our view to match design constraints. For example, we may for one reason or another want to create a fake insetGrouped style which is managed by the cell. By having the view and its logic handled elsewhere, we can simplify the process of customization.

Composition

When built this way, more complex views can be composed together. Moreover, the ViewModels can be composed together, providing a much more robust and reusable solution. Since each view can be tested in isolation or when integrated, more of the application can be tested at a time.

The New iOS 14 Content Configuration API

In iOS 14, Apple introduced the Content Configuration API, which provides a new way to customize the appearance and behavior of table and collection view cells. The Content Configuration API is designed to work with custom UIViews that are embedded in the cell’s contentView. When you use the Content Configuration API, you can specify the layout and behavior of your custom UI elements, while still leveraging the cell’s caching and reuse mechanisms.

However, I don’t prefer to use this API as we cannot be specific over which implementation of each protocol is accepted by a particular cell or view. As a result, it can lead to an over-abundance of type checking in production code.

Conclusion

In this blog post, we’ve explored why you should prefer to build UIViews to be embedded in UITableViewCell or UICollectionViewCell instead of working directly with the cell. We’ve covered the benefits of reusability and better separation of concerns, as well as performance considerations. We’ve also discussed the new iOS 14 Content Configuration API and how it can be used with custom subviews to achieve better customization and performance.

By building your custom UI elements as subviews, you can create modular and reusable code, which can save you time and effort in the long run. You can also take advantage of the performance benefits of table and collection view cell caching.