UICollectionViewListCell not resizing

Please run the following UIKit app.

It uses a collection view with compositional layout (list layout) and a diffable data source.

The collection view has one section with one row.

The cell contains a text field that is pinned to its contentView.

import UIKit

class ViewController: UIViewController {
    var collectionView: UICollectionView!
    
    var dataSource: UICollectionViewDiffableDataSource<String, String>!

    override func viewDidLoad() {
        super.viewDidLoad()

        configureHierarchy()
        configureDataSource()
    }

    func configureHierarchy() {
        collectionView = .init(frame: .zero, collectionViewLayout: createLayout())
        view.addSubview(collectionView)
        collectionView.frame = view.bounds
    }
    
    func createLayout() -> UICollectionViewLayout {
        UICollectionViewCompositionalLayout { section, layoutEnvironment in
            let config = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
            return NSCollectionLayoutSection.list(using: config, layoutEnvironment: layoutEnvironment)
        }
    }
    
    func configureDataSource() {
        let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, String> { cell, indexPath, itemIdentifier in
            let textField = UITextField()
            textField.placeholder = "Placeholder"
            textField.font = .systemFont(ofSize: 100)
            
            cell.contentView.addSubview(textField)
            textField.pinToSuperview()
        }
        
        dataSource = .init(collectionView: collectionView) { collectionView, indexPath, itemIdentifier in
            collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: itemIdentifier)
        }
        
        var snapshot = NSDiffableDataSourceSnapshot<String, String>()
        snapshot.appendSections(["main"])
        snapshot.appendItems(["demo"])
        dataSource.apply(snapshot, animatingDifferences: false)
    }
}

extension UIView {
    func pin(
        to object: CanBePinnedTo,
        top: CGFloat = 0,
        bottom: CGFloat = 0,
        leading: CGFloat = 0,
        trailing: CGFloat = 0
    ) {
        self.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            self.topAnchor.constraint(equalTo: object.topAnchor, constant: top),
            self.bottomAnchor.constraint(equalTo: object.bottomAnchor, constant: bottom),
            self.leadingAnchor.constraint(equalTo: object.leadingAnchor, constant: leading),
            self.trailingAnchor.constraint(equalTo: object.trailingAnchor, constant: trailing),
        ])
    }
    
    func pinToSuperview(
        top: CGFloat = 0,
        bottom: CGFloat = 0,
        leading: CGFloat = 0,
        trailing: CGFloat = 0,
        file: StaticString = #file,
        line: UInt = #line
    ) {
        guard let superview = self.superview else {
            print(">> \(#function) failed in file: \(String.localFilePath(from: file)), at line: \(line): could not find \(Self.self).superView.")
            return
        }
        
        self.pin(to: superview, top: top, bottom: bottom, leading: leading, trailing: trailing)
    }
    
    func pinToSuperview(constant c: CGFloat = 0, file: StaticString = #file, line: UInt = #line) {
        self.pinToSuperview(top: c, bottom: -c, leading: c, trailing: -c, file: file, line: line)
    }
}

@MainActor
protocol CanBePinnedTo {
    var topAnchor: NSLayoutYAxisAnchor { get }
    var bottomAnchor: NSLayoutYAxisAnchor { get }
    var leadingAnchor: NSLayoutXAxisAnchor { get }
    var trailingAnchor: NSLayoutXAxisAnchor { get }
}

extension UIView: CanBePinnedTo { }
extension UILayoutGuide: CanBePinnedTo { }

extension String {
    static func localFilePath(from fullFilePath: StaticString = #file) -> Self {
        URL(fileURLWithPath: "\(fullFilePath)").lastPathComponent
    }
}

Unfortunately, as soon as I insert a leading view in the cell:

let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, String> { cell, indexPath, itemIdentifier in
    let contentView = cell.contentView
    let leadingView = UIView()
    leadingView.backgroundColor = .systemRed
    let textField = UITextField()
    textField.placeholder = "Placeholder"
    textField.font = .systemFont(ofSize: 100)
    
    contentView.addSubview(leadingView)
    contentView.addSubview(textField)
    
    leadingView.translatesAutoresizingMaskIntoConstraints = false
    textField.translatesAutoresizingMaskIntoConstraints = false
    
    NSLayoutConstraint.activate([
        leadingView.centerYAnchor.constraint(equalTo: contentView.layoutMarginsGuide.centerYAnchor),
        leadingView.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor),
        leadingView.widthAnchor.constraint(equalTo: contentView.layoutMarginsGuide.heightAnchor),
        leadingView.heightAnchor.constraint(equalTo: contentView.layoutMarginsGuide.heightAnchor),
        
        textField.topAnchor.constraint(equalTo: contentView.topAnchor),
        textField.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
        textField.leadingAnchor.constraint(equalTo: leadingView.trailingAnchor, constant: 16),
        textField.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor),
        textField.heightAnchor.constraint(greaterThanOrEqualToConstant: 44)
    ])
}

the cell does not self-size, and in particular it does not accomodate the text field:

What would be the best way to make the cell resize automatically?

Answered by Filippo02 in 786531022

Use UIListContentConfiguration:

import UIKit

class ViewController: UIViewController {
    var collectionView: UICollectionView!
    
    var dataSource: UICollectionViewDiffableDataSource<String, String>!

    override func viewDidLoad() {
        super.viewDidLoad()

        configureHierarchy()
        configureDataSource()
    }

    func configureHierarchy() {
        collectionView = .init(frame: .zero, collectionViewLayout: createLayout())
        view.addSubview(collectionView)
        collectionView.frame = view.bounds
    }
    
    func createLayout() -> UICollectionViewLayout {
        UICollectionViewCompositionalLayout { section, layoutEnvironment in
            let config = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
            return NSCollectionLayoutSection.list(using: config, layoutEnvironment: layoutEnvironment)
        }
    }
    
    func configureDataSource() {
        let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, String> { cell, indexPath, itemIdentifier in
            var contentConfig = CustomListContentConfiguration()
            contentConfig.placeholder = "Placeholder"
            cell.contentConfiguration = contentConfig
        }
        
        dataSource = .init(collectionView: collectionView) { collectionView, indexPath, itemIdentifier in
            collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: itemIdentifier)
        }
        
        var snapshot = NSDiffableDataSourceSnapshot<String, String>()
        snapshot.appendSections(["main"])
        snapshot.appendItems(["demo"])
        dataSource.apply(snapshot, animatingDifferences: false)
    }
}

class CustomListContentConfiguration: UIContentConfiguration {
    var placeholder: String?
    
    func makeContentView() -> UIView & UIContentView {
        return CustomListContentView(configuration: self)
    }
    
    func updated(for state: UIConfigurationState) -> Self {
        // Not handling state changes in this example, so just return self
        return self
    }
}

class CustomListContentView: UIView, UIContentView {
    var configuration: UIContentConfiguration
    
    init(configuration: UIContentConfiguration) {
        self.configuration = configuration
        super.init(frame: .zero)
        configureSubviews()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func configureSubviews() {
        guard let config = configuration as? CustomListContentConfiguration else { return }
        
        let leadingView = UIView()
        leadingView.backgroundColor = .systemRed
        let textField = UITextField()
        textField.placeholder = config.placeholder
        textField.font = .systemFont(ofSize: 100)
        
        addSubview(leadingView)
        addSubview(textField)
        
        leadingView.translatesAutoresizingMaskIntoConstraints = false
        textField.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([
            leadingView.centerYAnchor.constraint(equalTo: layoutMarginsGuide.centerYAnchor),
            leadingView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor),
            leadingView.widthAnchor.constraint(equalTo: layoutMarginsGuide.heightAnchor),
            leadingView.heightAnchor.constraint(equalTo: layoutMarginsGuide.heightAnchor),
            
            textField.topAnchor.constraint(equalTo: topAnchor),
            textField.bottomAnchor.constraint(equalTo: bottomAnchor),
            textField.leadingAnchor.constraint(equalTo: leadingView.trailingAnchor, constant: 16),
            textField.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor),
            textField.heightAnchor.constraint(greaterThanOrEqualToConstant: 44)
        ])
    }
}

Accepted Answer

Use UIListContentConfiguration:

import UIKit

class ViewController: UIViewController {
    var collectionView: UICollectionView!
    
    var dataSource: UICollectionViewDiffableDataSource<String, String>!

    override func viewDidLoad() {
        super.viewDidLoad()

        configureHierarchy()
        configureDataSource()
    }

    func configureHierarchy() {
        collectionView = .init(frame: .zero, collectionViewLayout: createLayout())
        view.addSubview(collectionView)
        collectionView.frame = view.bounds
    }
    
    func createLayout() -> UICollectionViewLayout {
        UICollectionViewCompositionalLayout { section, layoutEnvironment in
            let config = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
            return NSCollectionLayoutSection.list(using: config, layoutEnvironment: layoutEnvironment)
        }
    }
    
    func configureDataSource() {
        let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, String> { cell, indexPath, itemIdentifier in
            var contentConfig = CustomListContentConfiguration()
            contentConfig.placeholder = "Placeholder"
            cell.contentConfiguration = contentConfig
        }
        
        dataSource = .init(collectionView: collectionView) { collectionView, indexPath, itemIdentifier in
            collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: itemIdentifier)
        }
        
        var snapshot = NSDiffableDataSourceSnapshot<String, String>()
        snapshot.appendSections(["main"])
        snapshot.appendItems(["demo"])
        dataSource.apply(snapshot, animatingDifferences: false)
    }
}

class CustomListContentConfiguration: UIContentConfiguration {
    var placeholder: String?
    
    func makeContentView() -> UIView & UIContentView {
        return CustomListContentView(configuration: self)
    }
    
    func updated(for state: UIConfigurationState) -> Self {
        // Not handling state changes in this example, so just return self
        return self
    }
}

class CustomListContentView: UIView, UIContentView {
    var configuration: UIContentConfiguration
    
    init(configuration: UIContentConfiguration) {
        self.configuration = configuration
        super.init(frame: .zero)
        configureSubviews()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func configureSubviews() {
        guard let config = configuration as? CustomListContentConfiguration else { return }
        
        let leadingView = UIView()
        leadingView.backgroundColor = .systemRed
        let textField = UITextField()
        textField.placeholder = config.placeholder
        textField.font = .systemFont(ofSize: 100)
        
        addSubview(leadingView)
        addSubview(textField)
        
        leadingView.translatesAutoresizingMaskIntoConstraints = false
        textField.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([
            leadingView.centerYAnchor.constraint(equalTo: layoutMarginsGuide.centerYAnchor),
            leadingView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor),
            leadingView.widthAnchor.constraint(equalTo: layoutMarginsGuide.heightAnchor),
            leadingView.heightAnchor.constraint(equalTo: layoutMarginsGuide.heightAnchor),
            
            textField.topAnchor.constraint(equalTo: topAnchor),
            textField.bottomAnchor.constraint(equalTo: bottomAnchor),
            textField.leadingAnchor.constraint(equalTo: leadingView.trailingAnchor, constant: 16),
            textField.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor),
            textField.heightAnchor.constraint(greaterThanOrEqualToConstant: 44)
        ])
    }
}

UICollectionViewListCell not resizing
 
 
Q