Last active
May 17, 2019 03:48
-
-
Save chrisbrandow/00a234e7768616fb198353f833b0a8b7 to your computer and use it in GitHub Desktop.
CollectionView with centered cells
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
/// 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