Skip to content

Instantly share code, notes, and snippets.

@chrisbrandow
Last active May 17, 2019 03:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save chrisbrandow/00a234e7768616fb198353f833b0a8b7 to your computer and use it in GitHub Desktop.
Save chrisbrandow/00a234e7768616fb198353f833b0a8b7 to your computer and use it in GitHub Desktop.
CollectionView with centered cells
/// This class centers the elements that fit on a given row/column
/// ripped off from https://github.com/Coeur/CollectionViewCenteredFlowLayout
/// modified to simplify and to remove forced unwrapping
open class CollectionViewCenteredFlowLayout: UICollectionViewFlowLayout {
open override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
guard let elementsAttributes = super.layoutAttributesForElements(in: rect)
else { return nil }
guard let collectionView = collectionView
else { return elementsAttributes }
// we group copies of the elements from the same row/column
var representedElements = [UICollectionViewLayoutAttributes]()
var cells = [[UICollectionViewLayoutAttributes]]()
var previousFrame = CGRect?.none
let isVertical = scrollDirection == .vertical
for layoutAttributes in elementsAttributes {
guard layoutAttributes.representedElementKind == nil else {
representedElements.append(layoutAttributes)
continue
}
guard let currentItemAttributes = layoutAttributes.copy() as? UICollectionViewLayoutAttributes else { continue }
if let prevFrame = previousFrame {
let aRect = isVertical
? CGRect(x: -.greatestFiniteMagnitude, y: prevFrame.origin.y, width: .infinity, height: prevFrame.size.height)
: CGRect(x: prevFrame.origin.x, y: -.greatestFiniteMagnitude, width: prevFrame.size.width, height: .infinity)
let containerIsFull = !currentItemAttributes.frame.intersects(aRect)
if containerIsFull {
cells.append([])
}
}
cells[cells.endIndex - 1].append(currentItemAttributes)
previousFrame = currentItemAttributes.frame
}
let repositionedRemainingElements = cells.flatMap { group -> [UICollectionViewLayoutAttributes] in
guard var updated = self.originSpacing(for: group, collectionView: collectionView, isVertical: isVertical)
else { return group }
return group.map {
$0.frame.origin.x = updated.origin
updated.origin += ($0.frame.size.width + updated.spacing)
return $0
}
}
return representedElements + repositionedRemainingElements
}
}
extension UICollectionViewFlowLayout {
func originSpacing(for group: [UICollectionViewLayoutAttributes], collectionView: UICollectionView, isVertical: Bool) -> (origin: CGFloat, spacing: CGFloat)? {
guard let section = group.first?.indexPath.section
else { return nil }
let sectionInset = self.evaluatedSectionInset(for: section)
let minItemSpacing = self.evaluatedMinimumItemSpacing(for: section)
let insetSpan = isVertical
? sectionInset.left - sectionInset.right
: sectionInset.top - sectionInset.bottom
let spans = isVertical
? (collectionView.bounds.width - group.reduce(0) { $0 + $1.frame.size.width })
: (collectionView.bounds.height - group.reduce(0) { $0 + $1.frame.size.height })
let spanSum = insetSpan + spans - (CGFloat(group.count - 1)*minItemSpacing)
return (spanSum/2.0, minItemSpacing)
}
func evaluatedSectionInset(for section: Int) -> UIEdgeInsets {
guard let (collectionView, flowLayout) = collectionViewAndLayout(),
let evaluatedInset = flowLayout.collectionView?(collectionView, layout: self, insetForSectionAt: section)
else { return sectionInset }
return evaluatedInset
}
func evaluatedMinimumItemSpacing(for section: Int) -> CGFloat {
guard let (collectionView, flowLayout) = collectionViewAndLayout(),
let evaluatedMinimum = flowLayout.collectionView?(collectionView, layout: self, minimumInteritemSpacingForSectionAt: section)
else { return minimumInteritemSpacing }
return evaluatedMinimum
}
func collectionViewAndLayout() -> (UICollectionView, UICollectionViewDelegateFlowLayout)? {
guard let collectionView = self.collectionView,
let layout = collectionView.delegate as? UICollectionViewDelegateFlowLayout
else { return nil }
return (collectionView, layout)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment