You can do that in your registration block.
For example:
let headerRegistration = UICollectionView.SupplementaryRegistration
<TitleSupplementaryView>(elementKind: CompColumnsVC.sectionHeaderElementKind) {
(supplementaryView, string, indexPath) in
supplementaryView.label.text = "\(string) for section \(indexPath.section)"
// default background color / corner radius /
// text color / border color / border width
supplementaryView.backgroundColor = .lightGray
supplementaryView.layer.cornerRadius = 0.0
supplementaryView.layer.borderColor = UIColor.black.cgColor
supplementaryView.layer.borderWidth = 1.0
supplementaryView.label.textColor = .black
// specific background color / corner radius /
// text color / border color / border width
// for sections 0, 1, 2 (all the rest use default
switch indexPath.section {
case 0:
supplementaryView.backgroundColor = .cyan
supplementaryView.layer.cornerRadius = 6.0
case 1:
supplementaryView.backgroundColor = .systemBlue
supplementaryView.label.textColor = .white
supplementaryView.layer.cornerRadius = 12.0
case 2:
supplementaryView.backgroundColor = .yellow
supplementaryView.layer.cornerRadius = 16.0
supplementaryView.layer.borderWidth = 0.0
supplementaryView.layer.borderColor = UIColor.red.cgColor
default:
()
}
}
gives me this result:
Here's a complete example, based on
- Compositional Layout ->
- Advanced Layouts ->
- Supplementary Views ->
- Section Headers/Footers
from Apple's Implementing Modern Collection Views sample app:
Simple single-label collection view cell:
class SimpleCell: UICollectionViewCell {
let theLabel: UILabel = {
let v = UILabel()
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: 4.0),
theLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 8.0),
theLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -8.0),
theLabel.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -4.0),
])
contentView.layer.borderColor = UIColor(white: 0.9, alpha: 1.0).cgColor
contentView.layer.borderWidth = 1.0
}
}
Reusable view for section headers:
class TitleSupplementaryView: UICollectionReusableView {
let label = UILabel()
let bkgView = UIView()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() {
label.translatesAutoresizingMaskIntoConstraints = false
bkgView.translatesAutoresizingMaskIntoConstraints = false
bkgView.addSubview(label)
addSubview(bkgView)
NSLayoutConstraint.activate([
label.topAnchor.constraint(equalTo: bkgView.topAnchor, constant: 8.0),
label.leadingAnchor.constraint(equalTo: bkgView.leadingAnchor, constant: 8.0),
label.trailingAnchor.constraint(equalTo: bkgView.trailingAnchor, constant: -8.0),
label.bottomAnchor.constraint(equalTo: bkgView.bottomAnchor, constant: -8.0),
bkgView.topAnchor.constraint(equalTo: topAnchor, constant: 4.0),
bkgView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0.0),
bkgView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0.0),
bkgView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -4.0),
])
label.font = .systemFont(ofSize: 14.0, weight: .light)
bkgView.backgroundColor = .clear
}
}
Example view controller class:
class CustomizeHeadersVC: 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.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -8.0),
])
configureDataSource()
collectionView.delegate = self
}
static let sectionHeaderElementKind = "section-header-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: .fractionalWidth(1.0),
heightDimension: .absolute(44))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
section.interGroupSpacing = 3
section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 0, bottom: 6, trailing: 0)
let headerFooterSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
heightDimension: .estimated(44))
let sectionHeader = NSCollectionLayoutBoundarySupplementaryItem(
layoutSize: headerFooterSize,
elementKind: CustomizeHeadersVC.sectionHeaderElementKind, alignment: .top)
section.boundarySupplementaryItems = [sectionHeader]
let layout = UICollectionViewCompositionalLayout(section: section)
return layout
}
func configureDataSource() {
let cellRegistration = UICollectionView.CellRegistration<SimpleCell, Int> { (cell, indexPath, identifier) in
// Populate the cell with our item description.
cell.theLabel.text = "\(indexPath)" // "\(indexPath.section),\(indexPath.item)"
}
let headerRegistration = UICollectionView.SupplementaryRegistration
<TitleSupplementaryView>(elementKind: CustomizeHeadersVC.sectionHeaderElementKind) {
(supplementaryView, string, indexPath) in
supplementaryView.label.text = "Section Header for section \(indexPath.section)"
// default background color / corner radius /
// text color / border color / border width
supplementaryView.bkgView.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
supplementaryView.bkgView.layer.cornerRadius = 0.0
supplementaryView.bkgView.layer.borderColor = UIColor.black.cgColor
supplementaryView.bkgView.layer.borderWidth = 1.0
supplementaryView.label.textColor = .black
// specific background color / corner radius /
// text color / border color / border width
// for sections ... cycle through 4 "styles"
switch indexPath.section % 4 {
case 0:
supplementaryView.bkgView.backgroundColor = .cyan
supplementaryView.bkgView.layer.cornerRadius = 6.0
case 1:
supplementaryView.bkgView.backgroundColor = .systemBlue
supplementaryView.label.textColor = .white
supplementaryView.bkgView.layer.cornerRadius = 12.0
supplementaryView.bkgView.layer.borderWidth = 2.0
case 2:
supplementaryView.bkgView.backgroundColor = .yellow
supplementaryView.bkgView.layer.cornerRadius = 16.0
supplementaryView.bkgView.layer.borderWidth = 0.0
supplementaryView.bkgView.layer.borderColor = UIColor.red.cgColor
default:
()
}
}
dataSource = UICollectionViewDiffableDataSource<Int, Int>(collectionView: collectionView) {
(collectionView: UICollectionView, indexPath: IndexPath, identifier: Int) -> UICollectionViewCell? in
return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: identifier)
}
dataSource.supplementaryViewProvider = { (view, kind, index) in
return self.collectionView.dequeueConfiguredReusableSupplementary(
using: headerRegistration, for: index)
}
// initial data
let itemsPerSection = 3
let sections = Array(0..<25)
var snapshot = NSDiffableDataSourceSnapshot<Int, Int>()
var itemOffset = 0
sections.forEach {
snapshot.appendSections([$0])
snapshot.appendItems(Array(itemOffset..<itemOffset + itemsPerSection))
itemOffset += itemsPerSection
}
dataSource.apply(snapshot, animatingDifferences: false)
}
}
Output - we cycle through 4 different section header "styles" (background and text colors, borders, corner radii, etc):
Edit - after comments...
Slight modifications to above code to also show a "section background" decoration view...
Section background view:
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),
])
}
}
Example view controller class:
class CustomizeHeadersVC: 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.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -8.0),
])
configureDataSource()
collectionView.delegate = self
}
func collectionView(_ collectionView: UICollectionView, willDisplaySupplementaryView view: UICollectionReusableView, forElementKind elementKind: String, at indexPath: IndexPath) {
if elementKind == CustomizeHeadersVC.backgroundElementKind,
let v = view as? SectionBackgroundView
{
// default background color / corner radius /
// text color / border color / border width
v.bkgView.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
v.bkgView.layer.cornerRadius = 0.0
v.bkgView.layer.borderColor = UIColor.black.cgColor
v.bkgView.layer.borderWidth = 1.0
// specific background color / corner radius /
// text color / border color / border width
// for sections ... cycle through 4 "styles"
switch indexPath.section % 4 {
case 0:
v.bkgView.backgroundColor = .cyan
v.bkgView.layer.cornerRadius = 6.0
case 1:
v.bkgView.backgroundColor = .systemBlue
v.bkgView.layer.cornerRadius = 12.0
v.bkgView.layer.borderWidth = 2.0
case 2:
v.bkgView.backgroundColor = .systemYellow
v.bkgView.layer.cornerRadius = 16.0
v.bkgView.layer.borderWidth = 0.0
v.bkgView.layer.borderColor = UIColor.red.cgColor
default:
()
}
if let bc = v.bkgView.backgroundColor {
v.bkgView.backgroundColor = bc.withAlphaComponent(0.25)
}
}
}
static let sectionHeaderElementKind = "section-header-element-kind"
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: .fractionalWidth(1.0),
heightDimension: .absolute(44))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
section.interGroupSpacing = 3
section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 0, bottom: 6, trailing: 0)
section.contentInsets = NSDirectionalEdgeInsets(top: 8, leading: 20, bottom: 16, trailing: 20)
let headerFooterSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
heightDimension: .estimated(44))
let sectionHeader = NSCollectionLayoutBoundarySupplementaryItem(
layoutSize: headerFooterSize,
elementKind: CustomizeHeadersVC.sectionHeaderElementKind, alignment: .top)
section.boundarySupplementaryItems = [sectionHeader]
section.decorationItems = [
NSCollectionLayoutDecorationItem.background(elementKind: CustomizeHeadersVC.backgroundElementKind)
]
let config = UICollectionViewCompositionalLayoutConfiguration()
config.interSectionSpacing = 12 // section spacing
let layout = UICollectionViewCompositionalLayout(section: section, configuration: config)
layout.register(SectionBackgroundView.self, forDecorationViewOfKind: CustomizeHeadersVC.backgroundElementKind)
return layout
}
func configureDataSource() {
let cellRegistration = UICollectionView.CellRegistration<SimpleCell, Int> { (cell, indexPath, identifier) in
// Populate the cell with our item description.
cell.theLabel.text = "\(indexPath)" // "\(indexPath.section),\(indexPath.item)"
}
let bkgRegistration = UICollectionView.SupplementaryRegistration
<SectionBackgroundView>(elementKind: CustomizeHeadersVC.backgroundElementKind) {
(supplementaryView, string, indexPath) in
supplementaryView.bkgView.backgroundColor = .green
}
let headerRegistration = UICollectionView.SupplementaryRegistration
<TitleSupplementaryView>(elementKind: CustomizeHeadersVC.sectionHeaderElementKind) {
(supplementaryView, string, indexPath) in
supplementaryView.label.text = "Section Header for section \(indexPath.section)"
// default background color / corner radius /
// text color / border color / border width
supplementaryView.bkgView.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
supplementaryView.bkgView.layer.cornerRadius = 0.0
supplementaryView.bkgView.layer.borderColor = UIColor.black.cgColor
supplementaryView.bkgView.layer.borderWidth = 1.0
supplementaryView.label.textColor = .black
// specific background color / corner radius /
// text color / border color / border width
// for sections ... cycle through 4 "styles"
switch indexPath.section % 4 {
case 0:
supplementaryView.bkgView.backgroundColor = .cyan
supplementaryView.bkgView.layer.cornerRadius = 6.0
case 1:
supplementaryView.bkgView.backgroundColor = .systemBlue
supplementaryView.label.textColor = .white
supplementaryView.bkgView.layer.cornerRadius = 12.0
supplementaryView.bkgView.layer.borderWidth = 2.0
case 2:
supplementaryView.bkgView.backgroundColor = .systemYellow
supplementaryView.bkgView.layer.cornerRadius = 16.0
supplementaryView.bkgView.layer.borderWidth = 0.0
supplementaryView.bkgView.layer.borderColor = UIColor.red.cgColor
default:
()
}
}
dataSource = UICollectionViewDiffableDataSource<Int, Int>(collectionView: collectionView) {
(collectionView: UICollectionView, indexPath: IndexPath, identifier: Int) -> UICollectionViewCell? in
return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: identifier)
}
dataSource.supplementaryViewProvider = { (view, kind, index) in
return self.collectionView.dequeueConfiguredReusableSupplementary(
using: headerRegistration, for: index)
}
// initial data
let itemsPerSection = 3
let sections = Array(0..<25)
var snapshot = NSDiffableDataSourceSnapshot<Int, Int>()
var itemOffset = 0
sections.forEach {
snapshot.appendSections([$0])
snapshot.appendItems(Array(itemOffset..<itemOffset + itemsPerSection))
itemOffset += itemsPerSection
}
dataSource.apply(snapshot, animatingDifferences: false)
}
}
Result: