I don't know why it is so complicated to design cells that can adapt to its content. It shouldn't need that much code, I still don't understand why UIKit
can't handle this properly.
Anyway, here is my issue (I have edited the whole post):
I have an UICollectionViewCell
that contains an UITableView
.
Here is my sizeForItem
method :
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize {
var cellWidth: CGFloat = collectionView.bounds.size.width
var cellHeight: CGFloat = 0
let cellConfigurator = items[indexPath.item].cellConfigurator
if type(of: cellConfigurator).reuseId == "MoonCollectionViewCell" {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: type(of: cellConfigurator).reuseId, for: indexPath) as? MoonCollectionViewCell {
cell.contentView.layoutIfNeeded()
let size = cell.selfSizedTableView.intrinsicContentSize
cellHeight = size.height
}
}
return CGSize.init(width: cellWidth, height: cellHeight)
}
sizeForItem
is called before cellForItem
, that's the reason of the layoutIfNeeded
, because I couldn't get the correct intrinsic content size
.
I have removed the XIB as suggested, and designed my UICollectionViewCell within the Storyboard.
Here is my UICollectionViewCell designed within a Storyboard (only the UITableViewCell is designed in a XIB file)
I only added an UITableView within the UICollectionViewCell.
I want the UICollectionViewCell
to adapt its size according to the height of the tableView
.
Now here is my tableView
:
I have created a subclass of UITableView
(from this post)
class SelfSizedTableView: UITableView {
var maxHeight: CGFloat = UIScreen.main.bounds.size.height
override func reloadData() {
super.reloadData()
self.invalidateIntrinsicContentSize()
self.layoutIfNeeded()
}
override var intrinsicContentSize: CGSize {
let height = min(contentSize.height, maxHeight)
return CGSize(width: contentSize.width, height: height)
}
}
Please note that I have disabled scrolling
, I have dynamic prototype
for the tableView cells, the style is grouped
.
EDIT : Check the configure method, it comes from a protocol I used to configure in a generic way all my UICollectionViewCell
func configure(data: [MoonImages]) {
selfSizedTableView.register(UINib.init(nibName: "MoonTableViewCell", bundle: nil), forCellReuseIdentifier: "MoonTableViewCell")
selfSizedTableView.delegate = self
selfSizedTableView.dataSource = moonDataSource
var frame = CGRect.zero
frame.size.height = .leastNormalMagnitude
selfSizedTableView.tableHeaderView = UIView(frame: frame)
selfSizedTableView.tableFooterView = UIView(frame: frame)
selfSizedTableView.maxHeight = 240.0
selfSizedTableView.estimatedRowHeight = 40.0
selfSizedTableView.rowHeight = UITableView.automaticDimension
moonDataSource.data.addAndNotify(observer: self) { [weak self] in
self?.selfSizedTableView.reloadData()
}
moonDataSource.data.value = data
}
FYI the dataSource is a custom dataSource, with dynamic value (Generics) and the observer pattern, to reload the collection/tableView when the data is set.
I also have this warning when I launch the App.
[CollectionView] An attempt to update layout information was detected while already in the process of computing the layout (i.e. reentrant call). This will result in unexpected behaviour or a crash. This may happen if a layout pass is triggered while calling out to a delegate.
Any hints or advice on how I should handle this ?
Because I am facing a strange behavior, it's like my sizeForItem use random values. The UICollectionViewCell
height is not the same than my UITableView
intrinsic content size height.
If I have 2 rows within my UITableView
, the UICollectionView
is not always equal at this size. I really don't know how to achieve this...
Should I invalideLayout
?
Maybe it's not the answer you wanted, but here're my two cents. For your particular requirements, the better solution is moving away from UITableView
, and use UIStackView
or your custom container view.
Here's why:
UITableView
is a subclass of UIScrollView
, but since you've disabled its scrolling feature, you don't need a UIScrollView
.UITableView
is mainly used to reuse cells, to improve performance and make code more structured. But since you're making it as large as its content size, none of your cells are reused, so features of UITableView
is not taken any advantage of.Thus, actually you don't need and you should not use either UITableView
or UIScrollView
inside the UICollectionViewCell
for your requirements.
If you agree with above part, here're some learnings from our practices:
UIView
based custom view, instead of putting in UITableViewCell
or UICollectionViewCell
directly. Then add it to UITableViewCell
or UICollectionViewCell
's contentView
and setup constraints. With this structure, we can reuse our custom view in more scenarios.UITableView
, add them into a vertical UIStackView
, create constraints deciding UIStackView
's width. Auto layout will take care of the rest things.UICollectionViewCell
, to calculate the wanted height, inside preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes)
func of your cell, you can use contentView.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel)
to calculate the height, do some check and return. Also, remember to invalidate layout when the width of the UICollectionView
changes.