iOS 14 UICollectionView - Custom Cell Constraints

Viewed 357

I am trying to implement a simple list with custom cells using an UICollectionView on iOS 14. My custom cell should display a title and a corresponding value in the following style. Cell layout

But the actual result now looks something like this: Actual cell layout

I started to utilise the new way of configuring cells using the UICellConfigurationState.

My solution consists of:

  • A custom UIContentConfiguration
  • A custom UIContentView
  • A custom UICollectionViewListCell

Now I have trouble to see how I can constrain my custom UIContentView to be aligned with the cells separator layout guide. My code looks as follows:

// MARK: - UIContentConfiguration
struct DisplayCellContentConfiguration: UIContentConfiguration, Hashable {
    
    var title: String?
    var value: String?
    
    func makeContentView() -> UIView & UIContentView {
        return DisplayCellContentView(configuration: self)
    }
    
    func updated(for state: UIConfigurationState) -> Self {
        
        // Perform update on parameters that does not related to cell's data items
        // Make sure we are dealing with instance of UICellConfigurationState
        guard state is UICellConfigurationState else {
            return self
        }
        
        // Update self based on the current state
        let updatedConfiguration = self
        
        return updatedConfiguration
    }
}

// MARK: - Content View
class DisplayCellContentView: UIView, UIContentView {
    
    // MARK: - UI Properties
    private lazy var stackView = UIStackView().with {
        $0.translatesAutoresizingMaskIntoConstraints = false
        $0.axis = .horizontal
        $0.distribution = .equalSpacing
        $0.isLayoutMarginsRelativeArrangement = true
    }
    
    private lazy var titleLabel = UILabel().with {
        $0.translatesAutoresizingMaskIntoConstraints = false
        $0.textColor = .label
        $0.font = UIFont.preferredFont(forTextStyle: .body)
        $0.adjustsFontForContentSizeCategory = true
    }
    
    private lazy var valueLabel = UILabel().with {
        $0.translatesAutoresizingMaskIntoConstraints = false
        $0.textColor = .secondaryLabel
        $0.font = UIFont.preferredFont(forTextStyle: .body)
        $0.adjustsFontForContentSizeCategory = true
    }
    
    // MARK: - UIContentView conforming
    private var currentConfiguration: DisplayCellContentConfiguration!
    var configuration: UIContentConfiguration {
        get {
            currentConfiguration
        }
        set {
            // Make sure the given configuration is of type DisplaySettingsCellContentConfiguration
            guard let newConfiguration = newValue as? DisplayCellContentConfiguration else {
                return
            }
            
            // Apply the new configuration
            // also update currentConfiguration to newConfiguration
            apply(configuration: newConfiguration)
        }
    }
    
    // MARK: - Initializers
    init(configuration: DisplayCellContentConfiguration) {
        super.init(frame: .zero)
        
        // Create the content view UI
        setupView()
        
        // Apply the configuration (set data to UI elements / define custom content view appearance)
        apply(configuration: configuration)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

// MARK: - Content View UI Setup
private extension DisplayCellContentView {
    
    private func setupView() {
    
        //TODO:  I would expect leading to be 16 here, but this does not work on real devices. In the simulator 16 is correct, on real devices it is 20
        addSubview(stackView)
        NSLayoutConstraint.activate([
            stackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20),
            stackView.topAnchor.constraint(equalTo: topAnchor, constant: 11),
            stackView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -11),
            stackView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -11)
        ])
        stackView.addArrangedSubview(titleLabel)
        stackView.addArrangedSubview(valueLabel)
    }
    
    private func apply(configuration: DisplayCellContentConfiguration) {
        
        // Only apply configuration if new configuration and current configuration are not the same
        guard currentConfiguration != configuration else {
            return
        }
        
        // Replace current configuration with new configuration
        currentConfiguration = configuration
        
        // Set data to UI elements
        titleLabel.text = configuration.title
        valueLabel.text = configuration.value
    }
}

// MARK: - UICollectionViewListCell

/// A UICollectionViewListCell that displays a title on the left and a corresponding value on the right side of the cell
class DisplayListCell: UICollectionViewListCell {
    
    var item: DisplayValueCell?
    
    override func updateConfiguration(using state: UICellConfigurationState) {
        // Create new configuration object and update it base on state
        var newConfiguration = DisplayCellContentConfiguration().updated(for: state)
        
        // Update any configuration parameters related to data item
        newConfiguration.title = item?.title
        newConfiguration.value = item?.value
        
        // Set content configuration in order to update custom content view
        contentConfiguration = newConfiguration
    }
}

The issue I see in this code is in DisplayCellContentView.setupView(). The layout constraint values here are rather guessed than really known and additionally only work on a real device but not in the simulator. So my question is, how can I consistently align my custom cell view with the separator layout guide leading anchor ?

Any help is appreciated !

0 Answers
Related