Headers with compositionalLayout

I'm trying to add headers and footers in a collectionView built as compositional.


Colmlection is built perfectly, but no supplementary func is never called: neither viewForSupplementaryElementOfKind nor referenceSizeForHeaderInSection or referenceSizeForFooterInSection


So I must miss some obvious declaration…



I've set the delegates, datasource, regitered classes, made room for headers and footers (did I did this correctly though ?).


Everything is for the time created in code, for easier testing.


import UIKit

class QuickCell4: UICollectionViewCell {

    let containerView: UIView = {
        let view = UIView()
        view.backgroundColor = .white
        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    }()


    override init(frame: CGRect) {
        super.init(frame: frame)
        configureViews()
    }

    func configureViews() {
        addSubview(containerView)
        containerView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
        containerView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
        containerView.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
        containerView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

class MyHeaderFooterClass: UICollectionReusableView {

    let titleLabel = UILabel()

    override init(frame: CGRect) {
        super.init(frame: frame)
        self.backgroundColor = UIColor.purple
    
        // Customize here
        addSubview(titleLabel)
        print("MyHeaderFooterClass")
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        titleLabel.sizeToFit()
        titleLabel.frame.origin = CGPoint(x: 15, y: 10) 
    }
}

class FourthViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {

    var collectionView: UICollectionView!

    let dataColors = [
        [UIColor.red, UIColor.blue, UIColor.green, UIColor.magenta, UIColor.purple, UIColor.orange, UIColor.black, UIColor.lightGray, UIColor.blue],
        [UIColor.red, UIColor.blue, UIColor.green, UIColor.magenta, UIColor.blue]
    ]

    let collectionViewHeaderFooterReuseIdentifier = "MyHeaderFooterClass"

    func createCustomLayout() -> UICollectionViewLayout {
    
        let layout = UICollectionViewCompositionalLayout { (section: Int, environment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection? in
        
            let leadingItem = NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: NSCollectionLayoutDimension.fractionalWidth(1.0), heightDimension: .fractionalHeight(1.0)))
            leadingItem.contentInsets = NSDirectionalEdgeInsets(top: 5, leading: 5, bottom: 5, trailing: 5)
            let leadingGroupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.7), heightDimension: .fractionalHeight(1))
            let leadingGroup = NSCollectionLayoutGroup.vertical(layoutSize: leadingGroupSize, subitem: leadingItem, count: 1)
        
            let trailingItem =  NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize(widthDimension: NSCollectionLayoutDimension.fractionalWidth(1.0), heightDimension: .fractionalHeight(1.0)))
            trailingItem.contentInsets = NSDirectionalEdgeInsets(top: 5, leading: 5, bottom: 5, trailing: 5)
            let trailingGroupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.3), heightDimension: .fractionalHeight(1))
        
            let trailingGroup =  NSCollectionLayoutGroup.vertical(layoutSize: trailingGroupSize, subitem: trailingItem, count: 2)
        
            let containerGroupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),  heightDimension: .absolute(250))
        
            let containerGroup = NSCollectionLayoutGroup.horizontal(layoutSize: containerGroupSize, subitems: [leadingGroup, trailingGroup])
        
            let section = NSCollectionLayoutSection(group: containerGroup)
            section.orthogonalScrollingBehavior = .continuousGroupLeadingBoundary
            section.contentInsets = NSDirectionalEdgeInsets(top: 20, leading: 0, bottom: 20, trailing: 0)
            // section.supplementariesFollowContentInsets = true
        
            return section
        }
        return layout
    }

    override func viewDidLoad() {
    
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    
        collectionView = UICollectionView(frame: .zero, collectionViewLayout: createCustomLayout())
        collectionView.backgroundColor = .systemTeal // .white
        self.collectionView.delegate = self
        self.collectionView.dataSource = self
    
        self.collectionView.register(QuickCell4.self, forCellWithReuseIdentifier: "cellID4")
    
        self.collectionView.register(MyHeaderFooterClass.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: collectionViewHeaderFooterReuseIdentifier)
    
        self.collectionView.register(MyHeaderFooterClass.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter, withReuseIdentifier: collectionViewHeaderFooterReuseIdentifier)
     // tried adding the following, no change
//        if let flowLayout = self.collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
//            flowLayout.sectionFootersPinToVisibleBounds = true
//        }

        configureCollectionView()

    }

    func configureCollectionView() {
        collectionView.translatesAutoresizingMaskIntoConstraints = false
        self.view.addSubview(collectionView)
    
        NSLayoutConstraint.activate([
            self.collectionView.topAnchor.constraint(equalTo: self.view.layoutMarginsGuide.topAnchor),
            self.collectionView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
            self.collectionView.leftAnchor.constraint(equalTo: self.view.leftAnchor),
            self.collectionView.rightAnchor.constraint(equalTo: self.view.rightAnchor)
        ])
    }

    func numberOfSections(in collectionView: UICollectionView) -> Int {
        print("Sections: ", dataColors.count)
        return dataColors.count
    }

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return dataColors[section].count
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellID4", for: indexPath) as? QuickCell4 {
            let colorArray = dataColors[indexPath.section]
        
            cell.containerView.backgroundColor = colorArray[indexPath.row]
            return cell
        } else {
            return UICollectionViewCell()
        }
    }


    func collectionView(_ collectionView: UICollectionView,
                        viewForSupplementaryElementOfKind kind: String,
                        at indexPath: IndexPath) -> UICollectionReusableView {
        print("UICollectionViewDelegateFlowLayout")
        switch kind {
        
        case UICollectionView.elementKindSectionHeader:
            let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: collectionViewHeaderFooterReuseIdentifier, for: indexPath) as! MyHeaderFooterClass
        
            headerView.backgroundColor = UIColor.blue
    
        headerView.titleLabel.text = "Header"
            return headerView
        
        case UICollectionView.elementKindSectionFooter:
            let footerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: collectionViewHeaderFooterReuseIdentifier, for: indexPath) as! MyHeaderFooterClass
        
            footerView.backgroundColor = UIColor.green
            footerView.titleLabel.text = "Footer"
            return footerView
        
        default:
            assert(false, "Unexpected element kind")
        }
    }

    // Step 6: Handle size / make it appear:

   // Does it needs to be declared @objc
     func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
        print("referenceSizeForHeaderInSection")
        return CGSize(width: collectionView.frame.width, height: 30.0)
    }

   // Does it needs to be declared @objc
   func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize {
        print("referenceSizeForFooterInSection")
        return CGSize(width: 60.0, height: 30.0)
    }

    func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
    
        let layoutAttributes = UICollectionViewLayoutAttributes(forSupplementaryViewOfKind: elementKind, with: indexPath)
    
        if elementKind == UICollectionView.elementKindSectionHeader {
            layoutAttributes.frame = CGRect(x: 0.0, y: 0.0, width: 200, height: 50) //  contentWidth, height: headerHeight)
            //            layoutAttributes.zIndex = Int.max - 3
        }
    
        return layoutAttributes
    }

}

Credit: h ttps://hackernoon.com/complex-collection-view-layouts-in-swift-with-compositional-layout-z0bmk35kw


The only log I get is:

Sections: 2

Sections: 2

Answered by Claude31 in 394532022

Found what was missing.


In

func createCustomLayout() -> UICollectionViewLayout {


I had to add the definition of header and footer (could also add a left or right "leader" and "trailer"

            let footerHeaderSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
                                                          heightDimension: .absolute(50.0))
            let header = NSCollectionLayoutBoundarySupplementaryItem(
                layoutSize: footerHeaderSize,
                elementKind: UICollectionView.elementKindSectionHeader,
                alignment: .top)
            let footer = NSCollectionLayoutBoundarySupplementaryItem(
                layoutSize: footerHeaderSize,
                elementKind: UICollectionView.elementKindSectionFooter,
                alignment: .bottom)
            section.boundarySupplementaryItems = [header, footer]


This was really helful: h ttps://medium.com/flawless-app-stories/all-what-you-need-to-know-about-uicollectionviewcompositionallayout-f3b2f590bdbe

Accepted Answer

Found what was missing.


In

func createCustomLayout() -> UICollectionViewLayout {


I had to add the definition of header and footer (could also add a left or right "leader" and "trailer"

            let footerHeaderSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
                                                          heightDimension: .absolute(50.0))
            let header = NSCollectionLayoutBoundarySupplementaryItem(
                layoutSize: footerHeaderSize,
                elementKind: UICollectionView.elementKindSectionHeader,
                alignment: .top)
            let footer = NSCollectionLayoutBoundarySupplementaryItem(
                layoutSize: footerHeaderSize,
                elementKind: UICollectionView.elementKindSectionFooter,
                alignment: .bottom)
            section.boundarySupplementaryItems = [header, footer]


This was really helful: h ttps://medium.com/flawless-app-stories/all-what-you-need-to-know-about-uicollectionviewcompositionallayout-f3b2f590bdbe

Wow, thanks. Thought I was never going to get an answer to this.
Headers with compositionalLayout
 
 
Q