Last active
April 28, 2017 10:51
-
-
Save tobitech/841f1ea2bd591a70ea92beb61789f3f5 to your computer and use it in GitHub Desktop.
Custom collection view layout that makes headers stick
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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