I am trying out the Modern collectionviews with the below code.
The only problem in this code is the section background height is filling from the last item . If I keep the collection view scrolling to vertical that works correctly.
class SectionDecorationViewController: UIViewController {
static let sectionBackgroundDecorationElementKind = "section-background-element-kind"
var currentSnapshot: NSDiffableDataSourceSnapshot<Int, Int>! = nil
var dataSource: UICollectionViewDiffableDataSource<Int, Int>! = nil
var collectionView: UICollectionView! = nil
var currentSelection: Bool = false
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "Section Background Decoration View"
configureHierarchy()
configureDataSource()
}
}
extension SectionDecorationViewController {
/// - Tag: Background
func createLayout() -> UICollectionViewLayout {
let sectionProvider = { [weak self] (sectionIndex: Int,
layoutEnvironment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in
guard let self = self else { return nil }
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
heightDimension: .absolute(44))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
// Get the number of items in the section
let itemCount = self.currentSnapshot.numberOfItems(inSection: sectionIndex)
// Custom group height based on the number of items
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
heightDimension: .absolute(CGFloat(itemCount) * 44))
let group = NSCollectionLayoutGroup.vertical(layoutSize: groupSize, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
section.orthogonalScrollingBehavior = .paging
section.interGroupSpacing = 0 // Remove inter-group spacing
section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0) // Remove content insets
// Add the background decoration item
let sectionBackgroundDecoration = NSCollectionLayoutDecorationItem.background(
elementKind: SectionDecorationViewController.sectionBackgroundDecorationElementKind)
section.decorationItems = [sectionBackgroundDecoration]
return section
}
let config = UICollectionViewCompositionalLayoutConfiguration()
config.scrollDirection = .horizontal
config.interSectionSpacing = 0
let layout = UICollectionViewCompositionalLayout(
sectionProvider: sectionProvider, configuration: config)
layout.register(
SectionBackgroundDecorationView.self,
forDecorationViewOfKind: SectionDecorationViewController.sectionBackgroundDecorationElementKind)
return layout
}
}
extension SectionDecorationViewController {
func configureHierarchy() {
collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: createLayout())
collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
collectionView.backgroundColor = .systemBackground
view.addSubview(collectionView)
collectionView.delegate = self
}
func configureDataSource() {
let cellRegistration = UICollectionView.CellRegistration<ListCell, Int> { (cell, indexPath, identifier) in
// Populate the cell with our item description.
cell.label.text = "\(indexPath.section),\(indexPath.item)"
}
dataSource = UICollectionViewDiffableDataSource<Int, Int>(collectionView: collectionView) {
(collectionView: UICollectionView, indexPath: IndexPath, identifier: Int) -> UICollectionViewCell? in
// Return the cell.
return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: identifier)
}
// initial data
let itemsPerSection = 5
let sections = Array(0..<5)
currentSnapshot = NSDiffableDataSourceSnapshot<Int, Int>()
var itemOffset = 0
sections.forEach {
currentSnapshot.appendSections([$0])
currentSnapshot.appendItems(Array(itemOffset..<itemOffset + itemsPerSection))
itemOffset += itemsPerSection
}
dataSource.apply(currentSnapshot, animatingDifferences: false)
}
}
Based on this answer - https://stackoverflow.com/a/77297540/6257435 - with just a few modifications, and stripped-down to emphasize group backgrounds when .scrollDirection = .horizontal
...
Simple Cell class - single label, centered:
class SimpleCell: UICollectionViewCell {
let theLabel: UILabel = {
let v = UILabel()
v.textAlignment = .center
return v
}()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
theLabel.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(theLabel)
let g = contentView.layoutMarginsGuide
NSLayoutConstraint.activate([
theLabel.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
theLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
theLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
theLabel.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
])
contentView.layer.borderColor = UIColor(white: 0.5, alpha: 1.0).cgColor
contentView.layer.borderWidth = 1.0
contentView.layer.cornerRadius = 6
contentView.backgroundColor = UIColor(white: 0.95, alpha: 1.0)
}
}
Section Background View class:
class SectionBackgroundView: UICollectionReusableView {
static let reuseIdentifier: String = "SectionBackgroundView"
let bkgView = UIView()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() {
backgroundColor = .clear
bkgView.translatesAutoresizingMaskIntoConstraints = false
addSubview(bkgView)
NSLayoutConstraint.activate([
bkgView.topAnchor.constraint(equalTo: topAnchor, constant: 0.0),
bkgView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0.0),
bkgView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0.0),
bkgView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0.0),
])
bkgView.backgroundColor = .init(red: 0.5, green: 0.75, blue: 1.0, alpha: 1.0)
bkgView.layer.cornerRadius = 8.0
bkgView.layer.borderColor = UIColor.black.cgColor
bkgView.layer.borderWidth = 1.0
}
}
Example Controller class:
class HorizontalGroupsVC: UIViewController, UICollectionViewDelegate {
var collectionView: UICollectionView!
var dataSource: UICollectionViewDiffableDataSource<Int, Int>! = nil
override func viewDidLoad() {
super.viewDidLoad()
collectionView = UICollectionView(frame: .zero, collectionViewLayout: createLayout())
collectionView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(collectionView)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
collectionView.topAnchor.constraint(equalTo: g.topAnchor, constant: 8.0),
collectionView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 8.0),
collectionView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -8.0),
collectionView.heightAnchor.constraint(equalToConstant: 120.0),
])
configureDataSource()
collectionView.delegate = self
view.backgroundColor = .systemYellow
collectionView.backgroundColor = .clear
}
static let backgroundElementKind = "background-element-kind"
func createLayout() -> UICollectionViewLayout {
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalHeight(1.0))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
let groupSize = NSCollectionLayoutSize(widthDimension: .estimated(80.0),
heightDimension: .fractionalHeight(1.0))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
section.interGroupSpacing = 8
section.contentInsets = NSDirectionalEdgeInsets(top: 8, leading: 8, bottom: 8, trailing: 8)
section.decorationItems = [
NSCollectionLayoutDecorationItem.background(elementKind: HorizontalGroupsVC.backgroundElementKind)
]
let config = UICollectionViewCompositionalLayoutConfiguration()
config.interSectionSpacing = 12
config.scrollDirection = .horizontal
let layout = UICollectionViewCompositionalLayout(section: section, configuration: config)
layout.register(SectionBackgroundView.self, forDecorationViewOfKind: HorizontalGroupsVC.backgroundElementKind)
return layout
}
func configureDataSource() {
let cellRegistration = UICollectionView.CellRegistration<SimpleCell, Int> { (cell, indexPath, identifier) in
cell.theLabel.text = "\(indexPath)"
}
dataSource = UICollectionViewDiffableDataSource<Int, Int>(collectionView: collectionView) {
(collectionView: UICollectionView, indexPath: IndexPath, identifier: Int) -> UICollectionViewCell? in
return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: identifier)
}
// initial data
let itemsPerSection: [Int] = [3, 2, 1, 5, 3, 4, 2, 3, 4]
let sections = Array(0..<itemsPerSection.count)
var snapshot = NSDiffableDataSourceSnapshot<Int, Int>()
var itemOffset = 0
var i: Int = 0
sections.forEach {
snapshot.appendSections([$0])
let n = itemsPerSection[i % itemsPerSection.count]
snapshot.appendItems(Array(itemOffset..<itemOffset + n))
itemOffset += n
i += 1
}
dataSource.apply(snapshot, animatingDifferences: false)
}
}
Output: