Using NSCollectionLayoutSize with .estimated dimensions in horizontal orthogonal sections, creates layout issues. The cells & supplementary views have layout conflicts, the scroll behavior is sub optimal and spacing is not as expected
Working with Xcode: 12.4 , Simulator: iOS 14.4
Layout bug:
[LayoutConstraints] Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want.
Try this:
(1) look at each constraint and try to figure out which you don't expect;
(2) find the code that added the unwanted constraint or constraints and fix it.
(
"NSLayoutConstraint:0x6000011266c0 UIView:0x7fc6c4617020.height == 80 (active)",
"NSLayoutConstraint:0x600001126530 V:|-(0)-[UIView:0x7fc6c4617020] (active, names: '|':UIView:0x7fc6c4616d10 )",
"NSLayoutConstraint:0x6000011261c0 UIView:0x7fc6c4617020.bottom == UIView:0x7fc6c4616d10.bottom (active)",
"NSLayoutConstraint:0x600001121360 'UIView-Encapsulated-Layout-Height' UIView:0x7fc6c4616d10.height == 50 (active)"
)
Will attempt to recover by breaking constraint
NSLayoutConstraint:0x6000011266c0 UIView:0x7fc6c4617020.height == 80 (active)
Code to reproduce:
import UIKit
class ViewController: UIViewController {
lazy var collectionView: UICollectionView = {
let layout = createLayout()
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.dataSource = self
collectionView.backgroundColor = .systemBackground
collectionView.register(Cell.self, forCellWithReuseIdentifier: "cell")
collectionView.register(HeaderView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader,
withReuseIdentifier: "header")
return collectionView
}()
private func createLayout() - UICollectionViewCompositionalLayout {
let sectionProvider = { (section: Int,
layoutEnvironment: NSCollectionLayoutEnvironment) - NSCollectionLayoutSection? in
return self.horizontalLayout(layoutEnvironment: layoutEnvironment)
}
let config = UICollectionViewCompositionalLayoutConfiguration()
config.interSectionSpacing = 8
let layout = UICollectionViewCompositionalLayout(sectionProvider: sectionProvider, configuration: config)
return layout
}
private func supplementaryHeader() - NSCollectionLayoutBoundarySupplementaryItem {
let titleSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(50))
let titleSupplementary = NSCollectionLayoutBoundarySupplementaryItem(
layoutSize: titleSize,
elementKind: UICollectionView.elementKindSectionHeader,
alignment: .top)
return titleSupplementary
}
private func horizontalLayout(layoutEnvironment: NSCollectionLayoutEnvironment) - NSCollectionLayoutSection {
let size = NSCollectionLayoutSize(widthDimension: .estimated(120), heightDimension: .estimated(50))
let item = NSCollectionLayoutItem(layoutSize: size)
let group = NSCollectionLayoutGroup.horizontal(layoutSize: size, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
section.orthogonalScrollingBehavior = .continuous
section.interGroupSpacing = 8
section.contentInsets = NSDirectionalEdgeInsets(top: 16, leading: 16, bottom: 16, trailing: 16)
section.boundarySupplementaryItems = [supplementaryHeader()]
return section
}
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(collectionView)
NSLayoutConstraint.activate([
collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
collectionView.topAnchor.constraint(equalTo: view.topAnchor),
collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
}
}
// MARK: UICollectionViewDataSource
extension ViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) - UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
return cell
}
func numberOfSections(in collectionView: UICollectionView) - Int {
return 25
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) - Int {
return 4
}
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String,
at indexPath: IndexPath) - UICollectionReusableView {
switch kind {
case UICollectionView.elementKindSectionHeader:
let header: HeaderView = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader,
withReuseIdentifier: "header", for: indexPath) as! HeaderView
return header
default: fatalError()
}
}
}
class Cell: UICollectionViewCell {
lazy var view: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .systemRed
return view
}()
override init(frame: CGRect) {
super.init(frame: frame)
configure()
}
required init?(coder: NSCoder) {
fatalError("not implemented")
}
func configure() {
contentView.addSubview(view)
view.heightAnchor.constraint(equalToConstant: 80).isActive = true
view.widthAnchor.constraint(equalToConstant: 100).isActive = true
NSLayoutConstraint.activate([
view.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
view.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
view.topAnchor.constraint(equalTo: contentView.topAnchor),
view.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
])
}
}
class HeaderView: UICollectionReusableView {
lazy var view: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .systemTeal
return view
}()
override init(frame: CGRect) {
super.init(frame: frame)
configure()
}
required init?(coder: NSCoder) {
fatalError("not implemented")
}
func configure() {
addSubview(view)
view.heightAnchor.constraint(equalToConstant: 60).isActive = true
NSLayoutConstraint.activate([
view.leadingAnchor.constraint(equalTo: self.leadingAnchor),
view.trailingAnchor.constraint(equalTo: self.trailingAnchor),
view.topAnchor.constraint(equalTo: self.topAnchor),
view.bottomAnchor.constraint(equalTo: self.bottomAnchor)
])
}
}