Featured image for content view article showing an interface build with UIContentView.

How to Use UIContentView in UIKit

For quite some time, the traditional way to implement cell views for tables and collections in UIKit was to subclass UITableViewCell or UICollectionViewCell. However, starting with iOS 14, Apple introduced UIContentView and UIContentConfiguration, which allow developers to define custom content views for cells without subclassing. In practice, this turns out to be a very effective approach to implementing custom views for cells, and enables high interchangeability and reusability of these views.

In this article I’ll guide you through the process of creating custom cell views with the UIContentView and configuring them with the UIContentConfiguration.

What is Content View?

Table and collection views display data in cells. The UI elements of a cell are placed inside what is referred to as a content view. The content view of a cell in UIKit consists of two parts:

  • View—The definition of the content view as a subclass of the UIView that conforms to UIContentView protocol.
  • Configuration—The object that conforms to the UIContentConfiguration protocol and defines the content and settings of the view.

The view handles the creation of UI elements and their layout, while the configuration provides the content and styling for the view. The content view applies the configuration on the first load, and then whenever the data changes.

I established the following workflow for creating custom content views and their configurations:

Let’s take a closer look at each of these steps.

You can find the implementation of this UI in my sample project on GitHub. I used UIContentView to populate the UICollectionViewController with color picker, text field, and delete button. You literally can plug in any view you want into your table/collection view using UIContentView protocol.

I. Define Content View

First, consider what UI elements will constitute your view, such as labels, images, controls, etc. Start by creating a UIView subclass and declare your UI elements as its properties.

class CustomContentView: UIView {
    let label = UILabel()
    let imageView = UIImageView()
}

II. Conform to UIContentView Protocol

To make your custom content view fully functional within a table or collection view cell, it needs to conform to the UIContentView protocol. Adopt the UIContentView protocol by listing it after the UIView class.

class CustomContentView: UIView, UIContentView

To conform the view to UIContentView protocol declare a configuration variable property of type any UIContentConfiguration.

var configuration: any UIContentConfiguration

The keyword any in this case means that theoretically you could pass any configuration to create your custom content view, but in practice for every custom view there’s a dedicated configuration object.

III. Implement Simple Initializer

To provide initial configuration implement a simple initializer that takes an argument of the any UIContentConfiguration type. Make sure to call the dedicated init(frame:) initializer on the UIView super class. It’s fine to pass in the CGSize.zero in the super class initializer, because the actual size of the content view is set intrinsically by its subviews.

init(configuration: any UIContentConfiguration) {
    self.configuration = configuration
    super.init(frame: .zero)
    layOutViews()
}

The initializer is also a great place to lay out the UI elements within the content view. Since we’re subclassing the UIView here, we can take advantage of the direct access to its properties and methods for laying out the subviews. It’s very important to set proper constraints for the subviews of your content view, as these constraints are the key to ensure that UIKit is able to calculate the intrinsic size of the content view.

func layOutViews() {
    let padding: CGFloat = 16.0
    
    let stackView = UIStackView(arrangedSubviews: [imageView, label])
    stackView.axis = .horizontal
    stackView.spacing = padding / 2.0
    
    addSubview(stackView) // Using UIView methods directly
    
    stackView.translatesAutoresizingMaskIntoConstraints = false
    
    // Referring to UIView anchors directly
    NSLayoutConstraint.activate([
        stackView.topAnchor.constraint(equalTo: topAnchor, constant: padding),
        stackView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -padding),
        stackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: padding),
        stackView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -padding)
    ])
}

The UIView also requires the implementation of the init(coder:) initializer, however its implementation is irrelevant for the purposes of this article.

required init?(coder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}

IV. Define Content View Configuration

In context of custom content views in UIKit, the idea behind configuration is to keep the view and model data independent from each other. The configuration is essentially a helper structure that provides the content and updates the view due to changes in the data.

To create a configuration for content view, create a structure that conforms to UIContentConfiguration protocol, and declare in it the properties that provide content for the view. The UIContentConfiguration protocol requires the implementation of two methods:

  • makeContentView() — This method is called whenever the cell that contains the content view needs to appear on the screen. You can use this method to perform any checks or initial setup that you need to return the relevant view.
  • updated(for:) — The system calls this method when the state of the cell changes, such as when the cell is selected, disabled, etc. This method can be used to check the state that’s being passed by the system and return the configuration adjusted according to your needs.
extension CustomContentView {
    struct Configuration: UIContentConfiguration {
        var image: UIImage?
        var text: String?
        
        func makeContentView() -> any UIView & UIContentView {
            return CustomContentView(configuration: self)
        }
        
        func updated(for state: any UIConfigurationState) -> CustomContentView.Configuration {
            return self
        }
    }
}

V. Apply Configuration to Content View

var configuration: any UIContentConfiguration {
    didSet {
        configure(configuration)
    }
}

func configure(_ configuration: any UIContentConfiguration) {
    // 1. Check if the configuration type matches
    guard let configuration = configuration as? Configuration else { return }
    
    // 2. Apply your configuration to the views
    label.text = configuration.text
    imageView.image = configuration.image
}

What’s Next

At this point, the content view is ready to be used in table or collection view. To use content view in a cell simply create an instance of your configuration, fill the relevant configuration properties with your data, and assign your configuration to the contentConfiguration property of the cell wherever you handle the cell creation for your table/collection view.

// 1. Create instance of your configuration
var contentConfiguration = CustomContentView.Configuration()

// 2. Set relevant data
contentConfiguration.text = item.title
contentConfiguration.image = item.image

// 3. Set contentConfiguration of the cell to your configuration
cell.contentConfiguration = contentConfiguration

This code will work with any cell—be it UITableViewCell, UICollectionViewCell, so you can paste it wherever you need to configure your cells.

Tip: Extend Your Cell Types

You can streamline the process of initializing the right configuration by extending the cell view type your working with. Simple declare a function that returns the configuration you need.

extension UICollectionViewCell {
    func customContentViewConfiguration() -> CustomContentView.Configuration {
        return CustomContentView.Configuration()
    }
}

Using this extension method is as simple as calling it on your cell.

let cell: UICollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)

var contentConfiguration = cell.customContentViewConfiguration()

Wrap Up

Discover more from Dudee

Subscribe now to keep reading and get access to the full archive.

Continue reading