I am working on a project where I am using UICollectionView with a compositional layout. I am trying to add corner radius to the section headers of my UICollectionView. I am using UICollectionViewCompositionalLayout to create sections, and I want each section header to have a different corner radius, color, and design.
Here is an example of my code:
// Creating the compositional layout
let layout = UICollectionViewCompositionalLayout { sectionIndex, layoutEnvironment in
// configuring sections and items
}
// Registering section header
let headerRegistration = UICollectionView.SupplementaryRegistration
<HeaderCollectionViewCell>(elementKind: UICollectionView.elementKindSectionHeader) {
supplementaryView, string, indexPath in
// configuring header view
}
collectionView.collectionViewLayout = layout
collectionView.register(HeaderCollectionViewCell.self,
forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader,
withReuseIdentifier: "HeaderCollectionViewCell")
How can I add different corner radius, colors, and designs for each section header of my UICollectionView using UICollectionViewCompositionalLayout? Any help or guidance on this issue would be greatly appreciated. Thank you!
I've attempted to decorate it by creating decorators within sections, but it seems that I can only change the colors and not the corner radius. Moreover, the colors I've added don't adhere to the section constraints. For instance, if I add horizontal padding to a section, the section color overflows beyond that padding and expands to the width of the screen
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
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: