I'm having a set of UICollectionViewCell subclasses with different layout (using AutoLayout).
Since "Self Sizing Cells" feature is completely broken, I calculate size manually in the DataSource. However, I've noticed that the calculated size is exactly 10 pt less than it should be, regardless of the cell.
Here is the code I use to calculate size:
let sizeCache = CellCache() // Size cache stores previously calculated values
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if let cachedSize = sizeCache.sizeAtIndexPath(indexPath: indexPath) {
return cachedSize // Return cached value if it exists
}
// Calculate the size if not found in the cache
let cellClass = cellTypeAt(indexPath: indexPath) // Get the class type
let cell = sizeCache.createCellOfTypeIfNeeded(cellType: cellClass) // Dequeue or instantiate a cell
self.collectionView(collectionView, cell: cell, configureAt: indexPath) // Ask DataSource to configure this cell
let fittingSize = CGSize(width: collectionView.bounds.width - 16, height: 0) // Set the padding to 16
cell.bounds = CGRect(origin: .zero, size: fittingSize) // Set cell's width
var result = cell.contentView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize) // Calculate cell's size using AutoLayout
result.width = collectionView.bounds.width - 16 // Set the padding
result.height = result.height + 10 // !!! Error - Add 10 pt to the result !!!
sizeCache.setSize(size: result, at: indexPath) // Cache the result
return result
}
Note the line at the end where I have to add 10 pt to make it work.
All my cells are a subclasses of this base class which sets the width
constraint:
import UIKit
import SnapKit
class AutoSizingCellBase: UICollectionViewCell {
override class var requiresConstraintBasedLayout: Bool {
return true
}
private final var widthConstraint: Constraint?
override func updateConstraints() {
if widthConstraint == nil {
if let window = window {
let width = window.bounds.width - 16
contentView.snp.makeConstraints { (make) in
widthConstraint = make.width.equalTo(width).constraint
}
}
}
super.updateConstraints()
}
}
The examples of the issue:
Correct sizing (after adding 10 pt)
Incorrect sizing (without adding 10 pt)
The issue affects all the cells. What could be the root cause of it?
Update: Constraints Example Here are the constraints I use to configure the views shown:
private func setupConstraints() {
price.setContentCompressionResistancePriority(.required, for: .horizontal)
disclosureIndicator.setContentCompressionResistancePriority(.required, for: .horizontal)
disclosureIndicator.setContentCompressionResistancePriority(.required, for: .vertical)
disclosureIndicator.setContentHuggingPriority(.required, for: .horizontal)
disclosureIndicator.setContentHuggingPriority(.required, for: .vertical)
title.snp.makeConstraints { (make) in
make.leading.top.equalTo(contentView.layoutMarginsGuide)
make.trailing.lessThanOrEqualTo(price.snp.leading).offset(-8)
}
subtitle.snp.makeConstraints { (make) in
make.top.equalTo(title.snp.bottom).offset(8)
make.leading.bottom.equalTo(contentView.layoutMarginsGuide)
}
disclosureIndicator.snp.makeConstraints { (make) in
make.trailing.equalTo(contentView.layoutMarginsGuide)
make.centerY.equalToSuperview()
}
price.snp.makeConstraints { (make) in
make.trailing.equalTo(disclosureIndicator.snp.leading).offset(-8)
make.centerY.equalToSuperview()
}
}
This is a rather complex example, but the issue is reproducible even with a more simpler ones:
private func setupConstraints() {
button.snp.makeConstraints { (make) in
make.width.equalToSuperview() // Button is a subview of the UIStackView
}
stack.snp.makeConstraints { (make) in
make.edges.equalTo(contentView.layoutMarginsGuide)
make.height.greaterThanOrEqualTo(150)
}
}
Update
Adding the offset in the constraint solves the issue, while the view hierarchy debugger still shows "Ambiguous layout":
subtitle.snp.makeConstraints { (make) in
make.top.equalTo(title.snp.bottom).offset(8)
make.leading.equalTo(contentView.layoutMarginsGuide)
make.bottom.equalTo(contentView.layoutMarginsGuide).offset(-10) // Subtracted 10
}
The question is where does the 10
comes from is still open.
Update 3
It seems that a part of the problem is in adjusting the layoutMargins
:
UIView.appearance().layoutMargins = UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16)
After I've removed the line above from the AppDelegate
, the size calculations became correct, although the looks of the cells changed.
So I'm thinking that the problem is for some reason the cells created for sizing have a different inset than the ones dequeued from the UICollectionView
.
LayoutMargins
are calculated differently for a UICollectionViewCell
when added to a superview and when not in a view hierarchy.
Because of the line:
UIView.appearance().layoutMargins = UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16)
Cells added to the superview had different margins than the cells created for sizing. Hence the difference.