Skip to content

Instantly share code, notes, and snippets.

@tobitech
Last active April 28, 2017 10:51
Show Gist options
  • Save tobitech/841f1ea2bd591a70ea92beb61789f3f5 to your computer and use it in GitHub Desktop.
Save tobitech/841f1ea2bd591a70ea92beb61789f3f5 to your computer and use it in GitHub Desktop.
Custom collection view layout that makes headers stick
//
// CFStickyHeadersCollectionViewFlowLayout.swift
// Ceflix
//
// Created by Tobi Omotayo on 08/02/2017.
// Copyright © 2017 Tobi Omotayo. All rights reserved.
//
import UIKit
class CFStickyHeadersCollectionViewFlowLayout: UICollectionViewFlowLayout {
// MARK: - Collection View Flow Layout Methods
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
return true
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
guard let layoutAttributes = super.layoutAttributesForElements(in: rect) else { return nil }
// Helpers
let sectionsToAdd = NSMutableIndexSet()
var newLayoutAttributes = [UICollectionViewLayoutAttributes]()
for layoutAttributesSet in layoutAttributes {
if layoutAttributesSet.representedElementCategory == .cell {
// Add Layout Attributes
newLayoutAttributes.append(layoutAttributesSet)
// Update Sections to Add
sectionsToAdd.add(layoutAttributesSet.indexPath.section)
} else if layoutAttributesSet.representedElementCategory == .supplementaryView {
// Update Sections to Add
sectionsToAdd.add(layoutAttributesSet.indexPath.section)
}
}
for section in sectionsToAdd {
let indexPath = IndexPath(item: 0, section: section)
if let sectionAttributes = self.layoutAttributesForSupplementaryView(ofKind: UICollectionElementKindSectionHeader, at: indexPath) {
newLayoutAttributes.append(sectionAttributes)
}
}
return newLayoutAttributes
}
override func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
guard let layoutAttributes = super.layoutAttributesForSupplementaryView(ofKind: elementKind, at: indexPath) else { return nil }
guard let boundaries = boundaries(forSection: indexPath.section) else { return layoutAttributes }
guard let collectionView = collectionView else { return layoutAttributes }
// Helpers
let contentOffsetY = collectionView.contentOffset.y
var frameForSupplementaryView = layoutAttributes.frame
let minimum = boundaries.minimum - frameForSupplementaryView.height
let maximum = boundaries.maximum - frameForSupplementaryView.height
if contentOffsetY < minimum {
frameForSupplementaryView.origin.y = minimum
} else if contentOffsetY > maximum {
frameForSupplementaryView.origin.y = maximum
} else {
frameForSupplementaryView.origin.y = contentOffsetY
}
layoutAttributes.frame = frameForSupplementaryView
return layoutAttributes
}
// MARK: - Helper Methods
func boundaries(forSection section: Int) -> (minimum: CGFloat, maximum: CGFloat)? {
// Helpers
var result = (minimum: CGFloat(0.0), maximum: CGFloat(0.0))
// Exit Early
guard let collectionView = collectionView else { return result }
// Fetch Number of Items for Section
let numberOfItems = collectionView.numberOfItems(inSection: section)
// Exit Early
guard numberOfItems > 0 else { return result }
if let firstItem = layoutAttributesForItem(at: IndexPath(item: 0, section: section)),
let lastItem = layoutAttributesForItem(at: IndexPath(item: (numberOfItems - 1), section: section)) {
result.minimum = firstItem.frame.minY
result.maximum = lastItem.frame.maxY
// Take Header Size Into Account
result.minimum -= headerReferenceSize.height
result.maximum -= headerReferenceSize.height
// Take Section Inset Into Account
result.minimum -= sectionInset.top
result.maximum += (sectionInset.top + sectionInset.bottom)
}
return result
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment