Self sizing in Custom subclass of UICollectionViewListCell not working in iOS 14

Viewed 786

Background:

iOS14 has introduced a new way to register and configure collectionViewCell and In Lists In CollectionView WWDC video apple developer says that self sizing is default to UICollectionViewListCell and we don't have to explicitly specify the height for cells. This works great if I use system list cell in various configurations but self sizing fails when I use it with custom subclass of UICollectionViewListCell

What have I tried?

iOS 14 has introduced a new way to configure the cells, where we don't access the cells components directly to set the various UI properties rater we use content configuration and background configuration to update/configure cells. This becomes little tricky when we use custom cells.

CustomSkillListCollectionViewCell

class CustomSkillListCollectionViewCell: UICollectionViewListCell {
    var skillLavel: String? {
        didSet {
            setNeedsUpdateConfiguration()
        }
    }
    
    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }

    override func updateConfiguration(using state: UICellConfigurationState) {
        backgroundConfiguration = SkillListViewBackgroundConfiguration.getBackgroundConfiguration(for: state)
        var content = SkillListViewContentConfiguration().updated(for: state)
        content.label = skillLavel
        contentConfiguration = content
    }
}

SkillListViewBackgroundConfiguration

struct SkillListViewBackgroundConfiguration {
    @available(iOS 14.0, *)
    static func getBackgroundConfiguration(for state: UICellConfigurationState) -> UIBackgroundConfiguration {
        var background = UIBackgroundConfiguration.clear()
        if state.isHighlighted || state.isSelected {
            background.backgroundColor = UIColor.green.withAlphaComponent(0.4)
        }
        else if state.isExpanded {
            background.backgroundColor = UIColor.red.withAlphaComponent(0.5)
        }
        else {
            background.backgroundColor = UIColor.red.withAlphaComponent(0.9)
        }
        return background
    }
}

SkillListViewContentConfiguration

struct SkillListViewContentConfiguration: UIContentConfiguration {
    var label: String? = nil
    
    @available(iOS 14.0, *)
    func makeContentView() -> UIView & UIContentView {
        return SkillListView(contentConfiguration: self)
    }
    
    @available(iOS 14.0, *)
    func updated(for state: UIConfigurationState) -> Self {
        guard let state = state as? UICellConfigurationState else {
            return self
        }
        let updatedConfig = self
        return updatedConfig
    }
}

Finally subview SkillListView

class SkillListView: UIView, UIContentView {
    var configuration: UIContentConfiguration {
        get {
            return self.appliedConfiguration
        }
        set {
            guard let newConfig = newValue as? SkillListViewContentConfiguration else { return }
            self.appliedConfiguration = newConfig
            apply()
        }
    }
    
    private var appliedConfiguration: SkillListViewContentConfiguration!
    var skillNameLabel: UILabel!
    
    @available(iOS 14.0, *)
    init(contentConfiguration: UIContentConfiguration) {
        super.init(frame: .zero)
        self.setUpUI()
        self.configuration = contentConfiguration
        self.apply()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    private func apply() {
        self.skillNameLabel.text = self.appliedConfiguration.label
    }
    
    private func setUpUI() {
        self.skillNameLabel = UILabel(frame: .zero)
        skillNameLabel.setContentCompressionResistancePriority(.defaultHigh, for: .vertical)
        skillNameLabel.setContentHuggingPriority(.defaultHigh, for: .vertical)
        self.skillNameLabel.translatesAutoresizingMaskIntoConstraints = false
        self.addSubview(skillNameLabel)
        NSLayoutConstraint.activate([
            self.skillNameLabel.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor, constant: 20),
            self.skillNameLabel.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor, constant: 20),
            self.skillNameLabel.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor, constant: 20)
        ])
    }
}

And I configure it using

    let skillsCellConfigurator = UICollectionView.CellRegistration<CustomSkillListCollectionViewCell, Employee> { (cell, indexPath, employee) in
        cell.skillLavel = employee.individualSkil
        cell.accessories = [.disclosureIndicator()]
    }

Issue:

Everything else works great except height

enter image description here

1 Answers

First, you need at least 1 more constraint to guarantee satisfaction as top, bottom, and leading needs a trailing or centerX to go with them. But more so, it is probably constraining to the margins rather than the superview. Cells should automatically respect margins like safe area as long as the UICollectionView itself respects them which I think is default behavior. The layoutmarginguide for cells is significantly pushed down on the top, which can be seen from a dummy example in interfacebuilder if you check.

Related