64

I need a label above every section in my Collection View. I've tried simply dragging a label into my header space above my prototype cell, but every time I run the app, the label is not visible.

Any help?

4 Answers4

105

To add the custom label above every section in UICollectionView, please follow below steps

  1. Enable the section header in UICollectionView

enter image description here

  1. Add a new file of type UICollectionReusableView
  2. In the storyboard change the class of section header in UICollectionViewCell to the newly added file of type UICollectionReusableView.
  3. Add a label in section header of UICollectionViewCell in storyboard
  4. Connect the label in the section header to the UICollectionReusableView file

    class SectionHeader: UICollectionReusableView {
        @IBOutlet weak var sectionHeaderlabel: UILabel!
    }
    
  5. In the ViewController add the below code

    func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
    
        if let sectionHeader = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "SectionHeader", for: indexPath) as? SectionHeader{
            sectionHeader.sectionHeaderlabel.text = "Section \(indexPath.section)"
            return sectionHeader
        }
        return UICollectionReusableView()
    }
    

Here "SectionHeader" is name of the file added to type UICollectionReusableView

Chanchal Raj
  • 4,176
  • 4
  • 39
  • 46
garg
  • 2,651
  • 1
  • 24
  • 21
  • 2
    Great step by step. The item 6 need a override prefix. – Phuah Yee Keat Nov 10 '18 at 06:56
  • 4
    In 1.,3., & 4. you refer to UICollectionViewCell, however I think it should be UICollectionView. – arcady bob Nov 15 '18 at 21:00
  • 1
    You will need this : https://stackoverflow.com/questions/29282447/unable-to-dequeue-a-cell-with-identifier-cell-must-register-a-nib-or-a-class-f – Kingsley Mitchell May 02 '19 at 01:16
  • 1
    You will also need to register the SectionHeader class with the collectionView:-self.collectionView.register(SectionHeader.self, forSupplementaryViewOfKind: kind, withReuseIdentifier: "SectionHeader") – wuf810 Jun 23 '21 at 17:06
  • 1
    Shouldn't "SectionHeader" be the reusable identifier set in storyboard? – Eric Apr 22 '22 at 07:24
45

Implement collectionView:viewForSupplementaryElementOfKind:atIndexPath: and supply a dequeued UICollectionElementKindSectionHeader containing your label. If this is a flow layout, be sure also to set the headerReferenceSize or you still won't see anything.

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • 7
    Example code here: https://github.com/mattneub/Programming-iOS-Book-Examples/blob/master/bk2ch08p462collectionViewFlowLayout/ch21p748collectionViewFlowLayout/ViewController.swift – matt May 02 '15 at 17:47
35

If you want a programmatic solution in Swift 4.2, you can do the following:

  1. Setup the UICollectionViewDelegate and UICollectionViewDelegateFlowLayout

  2. Make custom UICollectionReusableView subclasses with whatever views you want defined. Here's one for a header, you can create another for a footer with different characteristics:

    class SectionHeader: UICollectionReusableView {
         var label: UILabel = {
             let label: UILabel = UILabel()
             label.textColor = .white
             label.font = UIFont.systemFont(ofSize: 16, weight: .semibold)
             label.sizeToFit()
             return label
         }()
    
         override init(frame: CGRect) {
             super.init(frame: frame)
    
             addSubview(label)
    
             label.translatesAutoresizingMaskIntoConstraints = false
             label.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
             label.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 20).isActive = true
             label.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true
        }
    
        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    }
    
  3. Implement the viewForSupplementaryElementOfKind method with the custom views:

    func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { 
        if kind == UICollectionView.elementKindSectionHeader {
             let sectionHeader = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "header", for: indexPath) as! SectionHeader
             sectionHeader.label.text = "TRENDING"
             return sectionHeader
        } else { //No footer in this case but can add option for that 
             return UICollectionReusableView()
        }
    }
    
  4. Implement the referenceSizeForHeaderInSection and referenceSizeForFooterInSection methods. Header method shown below for example:

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
        return CGSize(width: collectionView.frame.width, height: 40)
    }
    
bradkratky
  • 1,577
  • 1
  • 14
  • 28
Ever Uribe
  • 639
  • 6
  • 13
  • 32
    You need to register the supplementary view - `self.collectionView.register(SectionHeader.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "header")` – brokenrhino May 09 '20 at 04:09
1

I know this is an old question that predates compositional layouts (as introduced in WWDC 2019 Advances in Collection View Layout), but in case you are trying to do this with compositional layout and diffable data sources:

  1. First, you would register your reuseIdentifier for your header view, like normal. E.g., if using a NIB:

    collectionView.register(
        UINib(nibName: "HeaderView", bundle: nil),
        forSupplementaryViewOfKind: "header",
        withReuseIdentifier: "HeaderView"
    )
    

    That presumes you have (a) a NIB for this supplementary view; and (b) a type with outlets for that view:

    class HeaderView: UICollectionReusableView {
        @IBOutlet weak var label: UILabel!
    }
    

    (Obviously, if you prefer programmatically creating your header view, then feel free to do that. You just want to make sure that you register that reuse identifier with your supplementary view.)

  2. Then, when configuring your compositional layout, you would create a “boundary supplementary view” (i.e., a NSCollectionLayoutBoundarySupplementaryItem) and set your section’s boundarySupplementaryItems:

    let itemSize = NSCollectionLayoutSize (…)
    let item = NSCollectionLayoutItem(layoutSize: itemSize)
    let groupSize = NSCollectionLayoutSize(…)
    let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
    let section = NSCollectionLayoutSection(group: group)
    
    let headerSize = NSCollectionLayoutSize (
        widthDimension: .fractionalWidth(1),
        heightDimension: .estimated(44)
    )
    
    let header = NSCollectionLayoutBoundarySupplementaryItem(
        layoutSize: headerSize,
        elementKind: "header",
        alignment: .top
    )
    header.pinToVisibleBounds = true
    
    section.boundarySupplementaryItems = [header]
    
    let layout = UICollectionViewCompositionalLayout(section: section)
    
    collectionView.collectionViewLayout = layout
    
  3. Then, assuming you are using a diffable data source, you would make sure to configure the supplementaryViewProvider of your UICollectionViewDiffableDataSource:

    supplementaryViewProvider = { [weak self] collectionView, kind, indexPath in
        guard let self else { return nil }
    
        let text = sections[indexPath.section].id
        let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "HeaderView", for: indexPath) as! HeaderView
        header.label.text = …
        return header
    }
    
Rob
  • 415,655
  • 72
  • 787
  • 1,044