i have declared collection view like below
lazy var collectionView:UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.itemSize = UICollectionViewFlowLayout.automaticSize
layout.minimumLineSpacing = 52
layout.sectionInset = UIEdgeInsets(top: 25, left: 0, bottom: 30, right: 0)
var collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.backgroundColor = UIColor.white
collectionView.showsVerticalScrollIndicator = false
collectionView.register(XXXCollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier)
collectionView.register(XXXHeaderViewCell.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: headerIdentifier)
collectionView.delegate = self
return collectionView
}()
and implemented below delegates , control comes only to referenceSizeForHeaderInSection method but not to viewForSupplementaryElementOfKind
public func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: headerIdentifier, for: indexPath)
return headerView
}
public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
let size = CGSize(width: self.collectionView.bounds.width, height: 200)
return size
}
Any help is really appreciated.
I was facing the same issue and then figured out a solution.
try to add #objc (collectionView:viewForSupplementaryElementOfKind:atIndexPath:) just above your func collectionView(viewForSupplementaryElementOfKind:at:).
It seems that the obj-C and Swift have different names.
Look at the last param, on obj-C we have atIndexPath while on Swift we have just at.
Was that helpful?
Please help, I've been trying to figure out why my code doesnt work. What I want to do is to have a custom header for my collection view but it seems that the class that I registered doesn't seem to trigger.
It doesn't even show the red box. It just shows a cyan blank background
What am I doing wrong?
Below are my codes:
class VideoDetailController : UICollectionViewController, UICollectionViewDelegateFlowLayout {
private let headerId = "headerId"
override func viewDidLoad() {
super.viewDidLoad()
collectionView?.backgroundColor = UIColor.cyan
collectionView?.register(VideoDetailHeader.self, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: headerId)
navigationItem.title = "Hello!"
}
override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionViewCell {
let header = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionElementKindSectionHeader, withReuseIdentifier: headerId, for: indexPath) as! VideoDetailHeader
return header
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
return CGSize(width: collectionView.frame.width,height:170)
}
}
class VideoDetailHeader: BaseCell {
override func setupViews() {
print("Setting up...")
super.setupViews()
backgroundColor = UIColor.red
}
}
BaseCell is UICollectionViewCell
It seems that the above codes work:
The only issue is that when I call this view controller, I used:
let layout = UICollectionViewLayout()
instead of :
let layout = UICollectionViewFlowLayout()
I have added the delegate function in the controller class but it won't called at the run time.
func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView {
}
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! UICollectionReusableView
headerView.backgroundColor = UIColor.blueColor();
return headerView
case UICollectionElementKindSectionFooter:
let footerView = collectionView.dequeueReusableSupplementaryViewOfKind(kind, withReuseIdentifier: "Footer", forIndexPath: indexPath) as! UICollectionReusableView
footerView.backgroundColor = UIColor.greenColor();
return footerView
default:
assert(false, "Unexpected element kind")
}
}
I follow this tutorial Appcoda tutorial and update code For swift 4.0
Register the class :
self.collectionView.register(HeadView.self, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "HeadView")
Delegates :
// For header size (required)
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
return CGSize(width:collectionView.frame.size.width, height:50.0)
}
func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
switch kind {
case UICollectionElementKindSectionHeader:
let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "HeadView", for: indexPath)
headerView.backgroundColor = UIColor.blue;
return headerView
default:
fatalError("Unexpected element kind")
}
}
Delegate method not called because you need to register a class for the supplementary view;
yourCollectionView.registerClass(UICollectionReusableView.self, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: "collectionHeaderID")
yourCollectionView.registerClass(UICollectionReusableView.self, forSupplementaryViewOfKind: UICollectionElementKindSectionFooter, withReuseIdentifier: "collectionFooterID")
If you use Custom UICollectionViewFlowLayout
set headerReferenceSize
let flowLayout = UICollectionViewFlowLayout()
flowLayout.scrollDirection = .vertical
flowLayout.minimumLineSpacing = 0
flowLayout.minimumInteritemSpacing = 0
flowLayout.sectionHeadersPinToVisibleBounds = true
flowLayout.headerReferenceSize = CGSize(width: self.collectionView.frame.size.width, height: 50)
My header seems not showing up even I already registered programmatically in UICollectionViewFlowLayout. Just blank.
My code
override func viewDidLoad() {
super.viewDidLoad()
UICollectionReusableViewlet layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
layout.itemSize = CGSize(width: 117, height: 117)
layout.minimumLineSpacing = 1
collectionView = UICollectionView(frame: self.view.frame, collectionViewLayout: layout)
collectionView!.dataSource = self
collectionView!.delegate = self
collectionView!.registerClass(EventHeaderView.self, forSupplementaryViewOfKind:UICollectionElementKindSectionHeader, withReuseIdentifier: "MyHeaderView");
collectionView!.registerClass(EventDetailCollectionViewCell.self, forCellWithReuseIdentifier:"MyCollectionViewCell")
collectionView!.backgroundColor = UIColor.blackColor()
self.view.addSubview(collectionView!)
}
func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView {
switch kind {
//2
case UICollectionElementKindSectionHeader:
//3
let headerView =
collectionView.dequeueReusableSupplementaryViewOfKind(kind,
withReuseIdentifier: "HeaderView",
forIndexPath: indexPath)
as! EventHeaderView
headerView.lblName.text = "Anniversary"
headerView.lblDescription.text = "Venue"
return headerView
default:
//4
assert(false, "Unexpected element kind")
}
}
Digging deeper from Ryan Lee's answer:
public func collectionView(_:UICollectionView, layout: UICollectionViewLayout, referenceSizeForHeaderInSection: Int) -> CGSize
{
if referenceSizeForHeaderInSection > 0 {
return CGSize.zero
}
return CGSize(width:0, height:50)
}
in case you have headers only for some sections, but not all of them
Assign datasource, delegate from UICollectionView to UIViewController.
Add custom UICollectionView class and assign it to UICollectionViewCell in storyboard. Also assign an identifier.
Set section header in inspector. (Image)
A header cell will be generated in collection view. Add another UICollectionViewCell class and assign this class to that cell. Also set an identifier.
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int{
return 9
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("customCell", forIndexPath: indexPath) as! CustomCollectionViewCell
cell.counterLabel.text = "\(indexPath.item)"
return cell
}
func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView {
switch kind {
case UICollectionElementKindSectionHeader:
let headerView =
collectionView.dequeueReusableSupplementaryViewOfKind(kind,withReuseIdentifier: "headerCell", forIndexPath: indexPath) as! HeaderCollectionViewCell
return headerView
default:
assert(false, "Unexpected element kind")
}
}
Here is link to github project.
Make sure you add this line in the layout:
layout.headerReferenceSize = CGSize(width:0, height:50)
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.
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")
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)
}
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
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.
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)
}
Xcode 11+ & iOS 13+
I am using Xib for creating Header & Footer Views
Create a Xibs and Class File For Header & Footer like this (Same as Footer class) & also create xib views for both
class HeaderViewCV: UICollectionReusableView {
}
Register Xib Cells like this
collectionView.register(UINib(nibName: "HeaderViewCV", bundle: nil), forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "HeaderViewCV") //elementKindSectionFooter for footerview
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")
}
}
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)
}
Done all the necessary requirement for Header & Footer in CollectionView is ready
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)
}
}
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)
}
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 {
}
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
}