I've been working on making the collection view list cell height bigger, and I was wondering how to make the cell height size bigger. Here is the code from apple's sample project. (Just changed a little bit.)
import UIKit
private enum Section: Hashable {
case main
}
private struct Category: Hashable {
let icon: UIImage?
let name: String?
static let music = Category(icon: UIImage(systemName: "music.mic"), name: "Music")
static let transportation = Category(icon: UIImage(systemName: "car"), name: "Transportation")
static let weather = Category(icon: UIImage(systemName: "cloud.rain"), name: "Weather")
}
private struct Item: Hashable {
let category: Category
let image: UIImage?
let title: String?
let description: String?
init(category: Category, imageName: String? = nil, title: String? = nil, description: String? = nil) {
self.category = category
if let systemName = imageName {
self.image = UIImage(systemName: systemName)
} else {
self.image = nil
}
self.title = title
self.description = description
}
private let identifier = UUID()
static let all = [
Item(category: .music, imageName: "headphones", title: "Headphones",
description: "A portable pair of earphones that are used to listen to music and other forms of audiooooofosdafosdaofdsfsdgfhsdfjhdsfghsdjgfhjdsgfhjsdgfhjsadgfhjsgfhjdsgfjhsgfhjsdgfgsdhjfgsdhjfgshjdkfghsjdfgjhdsfgjdhksgfshdkjafgjhdsgfhjsdagfhjsdagfhjsdgfhsjdgfhjkasdgfhjsadgfhjsagfhjsagfhdsajgfhsjdagfhjsdagfjshdgfhsdagfhjsgfhjsagfhjsagfhjsadgfhjdsagfhjsdgafhjsagdfhjdsaghfjgsadhfjasgdhfjksagfhsjadgfhjdskagfsjadhkfgdsjhkafghjsakgfhsdjakgfhjafgdsafjhkasdgjhfhs."),
Item(category: .music, imageName: "hifispeaker.fill", title: "Loudspeaker",
description: "Short.")
]
}
class CustomCellListViewController: UIViewController {
private var dataSource: UICollectionViewDiffableDataSource<Section, Item>! = nil
private var collectionView: UICollectionView! = nil
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "List with Custom Cells"
configureHierarchy()
configureDataSource()
}
}
extension CustomCellListViewController {
private func createLayout() -> UICollectionViewLayout {
let config = UICollectionLayoutListConfiguration(appearance: .plain)
return UICollectionViewCompositionalLayout.list(using: config)
}
}
extension CustomCellListViewController {
private func configureHierarchy() {
collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: createLayout())
collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.addSubview(collectionView)
collectionView.delegate = self
}
/// - Tag: CellRegistration
private func configureDataSource() {
let cellRegistration = UICollectionView.CellRegistration<CustomListCell, Item> { (cell, indexPath, item) in
cell.updateWithItem(item)
cell.accessories = [.disclosureIndicator()]
}
dataSource = UICollectionViewDiffableDataSource<Section, Item>(collectionView: collectionView) {
(collectionView: UICollectionView, indexPath: IndexPath, item: Item) -> UICollectionViewCell? in
return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: item)
}
// initial data
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
snapshot.appendSections([.main])
snapshot.appendItems(Item.all)
dataSource.apply(snapshot, animatingDifferences: false)
}
}
extension CustomCellListViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
collectionView.deselectItem(at: indexPath, animated: true)
}
}
// Declare a custom key for a custom `item` property.
fileprivate extension UIConfigurationStateCustomKey {
static let item = UIConfigurationStateCustomKey("com.apple.ItemListCell.item")
}
// Declare an extension on the cell state struct to provide a typed property for this custom state.
private extension UICellConfigurationState {
var item: Item? {
set { self[.item] = newValue }
get { return self[.item] as? Item }
}
}
// This list cell subclass is an abstract class with a property that holds the item the cell is displaying,
// which is added to the cell's configuration state for subclasses to use when updating their configuration.
private class ItemListCell: UICollectionViewListCell {
private var item: Item? = nil
func updateWithItem(_ newItem: Item) {
guard item != newItem else { return }
item = newItem
setNeedsUpdateConfiguration()
}
override var configurationState: UICellConfigurationState {
var state = super.configurationState
state.item = self.item
return state
}
}
private class CustomListCell: ItemListCell {
private func defaultListContentConfiguration() -> UIListContentConfiguration { return .subtitleCell() }
private lazy var listContentView = UIListContentView(configuration: defaultListContentConfiguration())
private let categoryIconView = UIImageView()
private let categoryLabel = UILabel()
private var customViewConstraints: (categoryLabelLeading: NSLayoutConstraint,
categoryLabelTrailing: NSLayoutConstraint,
categoryIconTrailing: NSLayoutConstraint)?
private func setupViewsIfNeeded() {
// We only need to do anything if we haven't already setup the views and created constraints.
guard customViewConstraints == nil else { return }
contentView.addSubview(listContentView)
contentView.addSubview(categoryLabel)
contentView.addSubview(categoryIconView)
listContentView.translatesAutoresizingMaskIntoConstraints = false
let defaultHorizontalCompressionResistance = listContentView.contentCompressionResistancePriority(for: .horizontal)
listContentView.setContentCompressionResistancePriority(defaultHorizontalCompressionResistance - 1, for: .horizontal)
categoryLabel.translatesAutoresizingMaskIntoConstraints = false
categoryIconView.translatesAutoresizingMaskIntoConstraints = false
let constraints = (
categoryLabelLeading: categoryLabel.leadingAnchor.constraint(greaterThanOrEqualTo: listContentView.trailingAnchor),
categoryLabelTrailing: categoryIconView.leadingAnchor.constraint(equalTo: categoryLabel.trailingAnchor),
categoryIconTrailing: contentView.trailingAnchor.constraint(equalTo: categoryIconView.trailingAnchor)
)
NSLayoutConstraint.activate([
listContentView.topAnchor.constraint(equalTo: contentView.topAnchor),
listContentView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
listContentView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
categoryLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
categoryIconView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
constraints.categoryLabelLeading,
constraints.categoryLabelTrailing,
constraints.categoryIconTrailing
])
customViewConstraints = constraints
}
private var separatorConstraint: NSLayoutConstraint?
private func updateSeparatorConstraint() {
guard let textLayoutGuide = listContentView.textLayoutGuide else { return }
if let existingConstraint = separatorConstraint, existingConstraint.isActive {
return
}
let constraint = separatorLayoutGuide.leadingAnchor.constraint(equalTo: textLayoutGuide.leadingAnchor)
constraint.isActive = true
separatorConstraint = constraint
}
/// - Tag: UpdateConfiguration
/// sets up the cell’s initial appearance and content
override func updateConfiguration(using state: UICellConfigurationState) {
setupViewsIfNeeded()
// Configure the list content configuration and apply that to the list content view.
var content = defaultListContentConfiguration().updated(for: state)
content.imageProperties.preferredSymbolConfiguration = .init(font: content.textProperties.font, scale: .large)
content.image = state.item?.image
content.text = state.item?.title
content.secondaryText = state.item?.description
content.axesPreservingSuperviewLayoutMargins = []
listContentView.configuration = content
// Get the list value cell configuration for the current state, which we'll use to obtain the system default
// styling and metrics to copy to our custom views.
let valueConfiguration = UIListContentConfiguration.valueCell().updated(for: state)
// Configure custom image view for the category icon, copying some of the styling from the value cell configuration.
categoryIconView.image = state.item?.category.icon
categoryIconView.tintColor = valueConfiguration.imageProperties.resolvedTintColor(for: tintColor)
categoryIconView.preferredSymbolConfiguration = .init(font: valueConfiguration.secondaryTextProperties.font, scale: .small)
// Configure custom label for the category name, copying some of the styling from the value cell configuration.
categoryLabel.text = state.item?.category.name
categoryLabel.textColor = valueConfiguration.secondaryTextProperties.resolvedColor()
categoryLabel.font = valueConfiguration.secondaryTextProperties.font
categoryLabel.adjustsFontForContentSizeCategory = valueConfiguration.secondaryTextProperties.adjustsFontForContentSizeCategory
// Update some of the constraints for our custom views using the system default metrics from the configurations.
customViewConstraints?.categoryLabelLeading.constant = content.directionalLayoutMargins.trailing
customViewConstraints?.categoryLabelTrailing.constant = valueConfiguration.textToSecondaryTextHorizontalPadding
customViewConstraints?.categoryIconTrailing.constant = content.directionalLayoutMargins.trailing
updateSeparatorConstraint()
}
}
When I run the code, if the secondary text is too short, the list cell becomes too small, and I want to make the cell height at least 80.

I tried putting another UIView in the cell and added text labels for both cell's text and secondarytext, and add UIimage to the cell since the UIView I put covers the content of the cell and I have to manually put title, secondary text, and image (and use autolayout to configure them). So how can I change the cell height without putting an additional UIView on the cell, so I can still use and display those values?
content.image = state.item?.image
content.text = state.item?.title
content.secondaryText = state.item?.description
This solution is a little involved, but essentially you can calculate and cache the size of your UICollectionViewCell, and provide a default height if its computed height is less than 80.
A contrived example for educational purposes: