69

How to make both header and footer in collection view in swift ?

I'm trying to combine a header and a footer together but it keep crashing, I couldn't find swift tutorial to understand it.

I don't how to return supplementary view for both rather just one .

I set them both on the storyboard (class + identifier )

override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
    //#warning Incomplete method implementation -- Return the number of sections
    return 2
}

override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    //#warning Incomplete method implementation -- Return the number of items in the section
    return 10
}

override func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView {
    var header: headerCell!
    var footer: footerCell!

    if kind == UICollectionElementKindSectionHeader {
        header =
            collectionView.dequeueReusableSupplementaryViewOfKind(kind,
                withReuseIdentifier: "header", forIndexPath: indexPath)
            as? headerCell
    }
    
    return header
}

Error: UICollectionElementKindCell with identifier one - must register a nib or a class for the identifier or connect a prototype cell in a storyboard'

override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> profileCC {
    let cell = collectionView.dequeueReusableCellWithReuseIdentifier("one", forIndexPath: indexPath) as! profileCC

    // Configure the cell

    return cell
}



override func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView {
    
    switch kind {
        
    case UICollectionElementKindSectionHeader:
        
        let headerView = collectionView.dequeueReusableSupplementaryViewOfKind(kind, withReuseIdentifier: "header", forIndexPath: indexPath) as! headerCell
        
        headerView.backgroundColor = UIColor.blueColor();
        return headerView
        
    case UICollectionElementKindSectionFooter:
        let footerView = collectionView.dequeueReusableSupplementaryViewOfKind(kind, withReuseIdentifier: "footer", forIndexPath: indexPath) as! footerCell
        
        footerView.backgroundColor = UIColor.greenColor();
        return footerView
        
    default:
        
        assert(false, "Unexpected element kind")
    }
}

I hope someone will help.

Mattia Righetti
  • 1,265
  • 1
  • 18
  • 31
marrioa
  • 1,265
  • 4
  • 14
  • 29

10 Answers10

103

You can make an UICollectionViewController to handle the UICollectionView and in Interface Builder activate the Footer and Header sections, then you can use the following method for preview in you UICollectionView the two sections added :

override func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView {
    
    switch kind {
        
    case UICollectionView.elementKindSectionHeader:
        
        let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "Header", for: indexPath)
        
        headerView.backgroundColor = UIColor.blue
        return headerView
        
    case UICollectionView.elementKindSectionFooter:
        let footerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "Footer", for: indexPath)
        
        footerView.backgroundColor = UIColor.green
        return footerView
        
    default:
        
        assert(false, "Unexpected element kind")
    }
}

In the above code I put the identifier for the footer and header as Header and Footer for example, you can do it as you want. If you want to create a custom header or footer then you need to create a subclass of UICollectionReusableView for each and customize it as you want.

You can register your custom footer and header classes in Interface Builder or in code with:

registerClass(myFooterViewClass, forSupplementaryViewOfKind: UICollectionElementKindSectionFooter, withReuseIdentifier: "myFooterView")
Victor Sigler
  • 23,243
  • 14
  • 88
  • 105
90

Updated for Swift 3+

Step 1:

In your view controller class, register the class to be used as a header, footer, or both:

let collectionViewHeaderFooterReuseIdentifier = "MyHeaderFooterClass"

Step 2:

If using a xib, use:

collectionView.register(UINib(nibName: collectionViewHeaderFooterReuseIdentifier bundle: nil), forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier:collectionViewHeaderFooterReuseIdentifier)

collectionView.register(UINib(nibName: collectionViewHeaderFooterReuseIdentifier bundle: nil), forSupplementaryViewOfKind: UICollectionElementKindSectionFooter, withReuseIdentifier:collectionViewHeaderFooterReuseIdentifier)

If not using a xib:

collectionView.register(MyHeaderFooterClass.self, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: collectionViewHeaderFooterReuseIdentifier)

collectionView.register(MyHeaderFooterClass.self, forSupplementaryViewOfKind: UICollectionElementKindSectionFooter, withReuseIdentifier: collectionViewHeaderFooterReuseIdentifier)

Step 3:

Create a custom header/footer class, implementation looks like:

import UIKit

class MyHeaderFooterClass: UICollectionReusableView {

 override init(frame: CGRect) {
    super.init(frame: frame)
    self.backgroundColor = UIColor.purple

    // Customize here

 }

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

 }
}

Step 4: If not using a xib, ignore

  • Create a new empty xib: "File --> New File --> Empty".

  • Name it the exact same name as the class. In this example: "MyHeaderFooterClass"

  • Add a collection reusable view to the xib.
  • Click on that object, select the Identity Inspector and change the class of that object to "MyHeaderFooterClass".

Step 5: - Support that new cell in your collection view via delegate method:

 func collectionView(_ collectionView: UICollectionView,
                    viewForSupplementaryElementOfKind kind: String,
                    at indexPath: IndexPath) -> UICollectionReusableView {

    switch kind {

    case UICollectionElementKindSectionHeader:
        let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: collectionViewHeaderFooterReuseIdentifier, for: indexPath)

        headerView.backgroundColor = UIColor.blue
        return headerView

    case UICollectionElementKindSectionFooter:
        let footerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: collectionViewHeaderFooterReuseIdentifier, for: indexPath)

        footerView.backgroundColor = UIColor.green
        return footerView

    default:
        assert(false, "Unexpected element kind")
    }
}

Step 6: Handle size / make it appear:

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
        return CGSize(width: collectionView.frame.width, height: 180.0)
}
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize {
        return CGSize(width: 60.0, height: 30.0)
}
mobilecat
  • 1,429
  • 13
  • 11
  • 4
    Perfect even for Swift 5 & if you're creating everything programmatically – Krekin Aug 03 '20 at 12:43
  • @mobilecat I am a little confused. In step 2 shouldn't the initialisations be vice versa for the conditions *if using a xib* and *if not using a xib* ? – Kozmotronik Dec 15 '21 at 11:31
  • for the size this worked for me: `flowLayout.headerReferenceSize = CGSize(CGSize(width: collectionView.frame.size.width, height: 100))` – Tung Fam Jun 15 '23 at 21:40
16

In addition to all answers above

To activate the Footer and Header sections

Select your collection then select Attributes inspector and check on the Footer and Header sections

like in photo

enter image description here

Gerges Eid
  • 1,031
  • 12
  • 18
9

Just to complement the remaining answers, don't forget to allocate space for your header/footer views, otherwise collectionView:viewForSupplementaryElementOfKind:atIndexPath won't be called.

Do so by implementing collectionView:layout:referenceSizeForHeaderInSection in your collectionView datasource.

Bruno Cunha
  • 1,680
  • 1
  • 17
  • 15
7

After using @mobilecat code you should use this function to make the header and footer appear

 func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
        return CGSize(width: collectionView.frame.width, height: 180.0)
    }
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize {
        return CGSize(width: 60.0, height: 30.0)
    }
Ahmed Abdallah
  • 2,338
  • 1
  • 19
  • 30
7

Xcode 11+ & iOS 13+

I am using Xib for creating Header & Footer Views

  1. Create a Xibs and Class File For Header & Footer like this (Same as Footer class) & also create xib views for both
class HeaderViewCV: UICollectionReusableView {

}
  1. Register Xib Cells like this
collectionView.register(UINib(nibName: "HeaderViewCV", bundle: nil), forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "HeaderViewCV") //elementKindSectionFooter for footerview
  1. Header & Footer View Methods
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
    
   switch kind {
                
   case UICollectionView.elementKindSectionHeader:
            let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "HeaderViewCV", for: indexPath)
            return headerView
            
    case UICollectionView.elementKindSectionFooter:
            let footerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "FooterViewCV", for: indexPath)
            return footerView
            
     default:
            assert(false, "Unexpected element kind")
    }
}
  1. For View Height Method for both
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
    return CGSize(width: self.collectionView.frame.width, height: 55)
}


func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize {
    return CGSize(width: self.collectionView.frame.width, height: 67)
}
  1. Done all the necessary requirement for Header & Footer in CollectionView is ready
Maulik Pandya
  • 2,200
  • 17
  • 26
Threadripper
  • 622
  • 10
  • 15
3

Solution

class CustomFlowLayout: UICollectionViewFlowLayout {

    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        let attributesForElementsInRect = super.layoutAttributesForElements(in: rect)
        var newAttributesForElementsInRect = [UICollectionViewLayoutAttributes]()

        for attributes in attributesForElementsInRect! {

            if !(attributes.representedElementKind == UICollectionElementKindSectionHeader
                || attributes.representedElementKind == UICollectionElementKindSectionFooter) {

                // cells will be customise here, but Header and Footer will have layout without changes.
            }

            newAttributesForElementsInRect.append(attributes)
        }

        return newAttributesForElementsInRect
    }
}


class YourViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        let headerNib = UINib.init(nibName: "HeaderCell", bundle: nil)
        collectionView.register(headerNib, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "HeaderCell")

        let footerNib = UINib.init(nibName: "FooterCell", bundle: nil)
        collectionView.register(footerNib, forSupplementaryViewOfKind: UICollectionElementKindSectionFooter, withReuseIdentifier: "FooterCell")
    }


    func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {

        switch kind {
        case UICollectionElementKindSectionHeader:
            let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "HeaderCell", for: indexPath) as! HeaderCell
            return headerView
        case UICollectionElementKindSectionFooter:
            let footerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "FooterCell", for: indexPath) as! FooterCell
            return footerView
        default:
            return UICollectionReusableView()
        }
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
        return CGSize(width: collectionView.frame.width, height: 45)
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize {
        return CGSize(width: collectionView.frame.width, height: 25)
    }
}
Zanael
  • 740
  • 5
  • 10
3

if you need to keep empty spaces for header and footer make an extension of UICollectionViewDelegateFlowLayout and use this code to set header or footer accordingly.

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    let footerValue = 150
    let headerValue = 150
    collectionView.contentInset = UIEdgeInsets(top: headerValue, left: 0, bottom: footerValue, right: 0)
}
Pramodya Abeysinghe
  • 1,098
  • 17
  • 13
  • Hey, how can we add something in footer of collectionView? E.g I have to add uilabel in footer of uicollection. From your method i am able to create footer but don't understand how to add it. Please help me – Abha Jan 09 '20 at 12:19
2

Note that your viewController must implement UICollectionViewDelegateFlowLayout or these methods won't get called

func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {

}

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {

}
1

following @mobilecat detailed answer, if you are using compositional collection view, add these lines when creating the layout:

let headerSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(44))
let headerElement = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: headerSize, elementKind: UICollectionView.elementKindSectionHeader, alignment: .top)

and set the headerElement to your section like this:

 let section = NSCollectionLayoutSection(group: group)
 section.boundarySupplementaryItems = [headerElement]

The full code snippet is:

private func createCompositionalLayout() -> UICollectionViewCompositionalLayout {
    let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalHeight(1.0),
                                          heightDimension: .fractionalHeight(0.2))
    let item = NSCollectionLayoutItem(layoutSize: itemSize)

    let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
                                           heightDimension: .fractionalWidth(1.0))
    
    let group = NSCollectionLayoutGroup.vertical(layoutSize: groupSize, subitems: [item])
    
    let headerSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(44))
    let headerElement = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: headerSize, elementKind: UICollectionView.elementKindSectionHeader, alignment: .top)

    let section = NSCollectionLayoutSection(group: group)
    section.boundarySupplementaryItems = [headerElement]


    let layout = UICollectionViewCompositionalLayout(section: section)
    return layout
  }
nivbp
  • 310
  • 1
  • 11