Good?
I have a problem with the implementation of a UICollectionview intro a UITableViewCell, because the height calculation of this component does not work properly, even though using updateConstraints()
and called by delegate tableView.beginUpdates()
, sometimes works correctly and sometimes renders missing height.
My layout is a table with a lot of cells, these cells have some texts and items in two collectionviews (I'm using UICollectionView because the size of these items needs to be dynamically)
The implementation of the UITableViewCell:
protocol CardTableViewCellDelegate: AnyObject {
func updateTableView()
}
class CardTableViewCell: UITableViewCell {
static let identifier: String = "CardTableViewCell"
lazy var cardView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.layer.cornerRadius = 8
view.layer.masksToBounds = true
view.clipsToBounds = true
view.layer.borderWidth = 1
view.layer.borderColor = UIColor.separator.cgColor
return view
}()
lazy var containerStackView: UIStackView = {
let stackView = UIStackView()
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical
stackView.spacing = 8
return stackView
}()
lazy var title: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = .preferredFont(forTextStyle: .headline)
label.numberOfLines = 0
return label
}()
lazy var subtitle: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = .preferredFont(forTextStyle: .subheadline)
label.numberOfLines = 0
return label
}()
lazy var sourceNew: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = .preferredFont(forTextStyle: .footnote)
label.numberOfLines = 0
return label
}()
var assetsCollectionView: AssetsCollectionView = {
let collectionView = AssetsCollectionView()
collectionView.translatesAutoresizingMaskIntoConstraints = false
return collectionView
}()
lazy var benchmarksCollectionView: BenchmarksCollectionView = {
let collectionView = BenchmarksCollectionView()
collectionView.translatesAutoresizingMaskIntoConstraints = false
return collectionView
}()
weak var delegate: CardTableViewCellDelegate?
// MARK: - Init
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupView()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Methods
func configure(with card: Card) {
title.text = card.title
subtitle.text = card.subtitle
sourceNew.text = card.sourceNew
configureAssetsCollectionView(with: card.assets)
configurebenchmarksCollectionView(with: card.benchmarks)
}
private func configureAssetsCollectionView(with model: [Asset]?) {
if let assets = model {
assetsCollectionView.configure(with: assets)
containerStackView.addArrangedSubview(assetsCollectionView)
assetsCollectionView.updateConstraints()
delegate?.updateTableView()
}
}
private func configurebenchmarksCollectionView(with model: [Benchmark]?) {
if let benchmarks = model {
benchmarksCollectionView.configure(with: benchmarks)
containerStackView.addArrangedSubview(benchmarksCollectionView)
}
}
}
// MARK: - ViewCode
extension CardTableViewCell {
func setupView() {
setupLayout()
setupHierarchy()
setupConstrains()
}
func setupLayout() {
backgroundColor = .white
cardView.backgroundColor = .clear
}
func setupHierarchy() {
[title,
subtitle,
sourceNew].forEach(containerStackView.addArrangedSubview(_:))
cardView.addSubview(containerStackView)
contentView.addSubview(cardView)
}
func setupConstrains() {
NSLayoutConstraint.activate([
cardView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 16),
cardView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
cardView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16),
cardView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -16),
containerStackView.topAnchor.constraint(equalTo: cardView.topAnchor, constant: 8),
containerStackView.leadingAnchor.constraint(equalTo: cardView.leadingAnchor, constant: 8),
containerStackView.trailingAnchor.constraint(equalTo: cardView.trailingAnchor, constant: -8),
containerStackView.bottomAnchor.constraint(equalTo: cardView.bottomAnchor, constant: -8),
assetsCollectionView.heightAnchor.constraint(greaterThanOrEqualToConstant: 22),
benchmarksCollectionView.heightAnchor.constraint(greaterThanOrEqualToConstant: 22)
])
}
}
and, the UICollectionView:
import UIKit
class AssetsCollectionView: UICollectionView {
private var assets: [Asset] = []
override var intrinsicContentSize: CGSize {
self.layoutIfNeeded()
print(contentSize)
return self.contentSize
}
init() {
let layout = LeftAlignedCollectionViewFlowLayout()
layout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
layout.minimumLineSpacing = 8
layout.minimumInteritemSpacing = 8
super.init(frame: .zero, collectionViewLayout: layout)
delegate = self
dataSource = self
register(AssetCollectionViewCell.self,
forCellWithReuseIdentifier: AssetCollectionViewCell.identifier)
showsHorizontalScrollIndicator = false
showsVerticalScrollIndicator = false
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configure(with assets: [Asset]) {
self.assets = assets
reloadData()
}
}
extension AssetsCollectionView: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return assets.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView
.dequeueReusableCell(withReuseIdentifier: AssetCollectionViewCell.identifier, for: indexPath) as? AssetCollectionViewCell else {
return UICollectionViewCell()
}
cell.configure(with: assets[indexPath.row])
return cell
}
}
So, can this component calculate your height automatically without using height calculation tricks? I tried using a lot of methods to send the cell to recalculate your constraints, but no one worked. :(
I know that I can use the number of items to calculate the height, but I need more powerful code to handle possible causes... any anyone help me with this?
The github url to reproduce locally: https://github.com/ramonfsk/MarketContextTimeline.git
systemLayoutSizeFitting(...)
in yourCardTableViewCell
class. Difficult to say, because we can't run your code to try it out. If you can put together a minimal reproducible example, we'll have a better chance of helping.UICollectionView
because the size of these items needs to be dynamically" -- are you planning to use any other collection view features? Such as selecting cells? Or, is this only to get the desired layout?UIView
subclass instead ofUICollectionView
. I will write it up as an answer tomorrow morning.