Skip to content

Instantly share code, notes, and snippets.

@romanfurman6
Created July 26, 2018 17:02
Show Gist options
  • Save romanfurman6/9e575d11b2026199785bad412eaa1bf7 to your computer and use it in GitHub Desktop.
Save romanfurman6/9e575d11b2026199785bad412eaa1bf7 to your computer and use it in GitHub Desktop.
class SadCollectionViewFlowLayout: UICollectionViewFlowLayout {
private let sideItemScale: CGFloat = 0.9
private let sideItemAlpha: CGFloat = 0.9
private let sideItemShift: CGFloat = 0.0
override open func prepare() {
super.prepare()
guard let collectionSize = collectionView?.bounds.size else { return }
let yInset = (collectionSize.height - itemSize.height) / 2
let xInset = (collectionSize.width - itemSize.width) / 2
self.sectionInset = UIEdgeInsets(top: yInset, left: xInset, bottom: yInset, right: xInset)
let side = itemSize.width
let scaledItemOffset = (side - side * 0.9) / 2
let fullSizeSideItemOverlap = 16.0 + scaledItemOffset
let inset = xInset
self.minimumLineSpacing = inset - fullSizeSideItemOverlap
}
override open func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
return true
}
override open func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
guard
let superAttributes = super.layoutAttributesForElements(in: rect),
let attributes = NSArray(array: superAttributes, copyItems: true) as? [UICollectionViewLayoutAttributes]
else { return nil }
return attributes.map { [unowned self] attribute in self.transformLayoutAttributes(attribute) }
}
private func transformLayoutAttributes(_ attributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
guard let collectionView = collectionView else { return attributes }
let collectionCenter = collectionView.frame.size.width / 2
let offset = collectionView.contentOffset.x
let normalizedCenter = attributes.center.x - offset
let maxDistance = itemSize.width + minimumLineSpacing
let distance = min(abs(collectionCenter - normalizedCenter), maxDistance)
let ratio = (maxDistance - distance)/maxDistance
let alpha = ratio * (1 - sideItemAlpha) + sideItemAlpha
let scale = ratio * (1 - sideItemScale) + sideItemScale
let shift = (1 - ratio) * sideItemShift
attributes.transform3D = CATransform3DScale(CATransform3DIdentity, scale, scale, 1)
attributes.zIndex = Int(alpha * 10)
attributes.center.y += shift
return attributes
}
override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
let collectionViewSize = collectionView!.bounds.size
let proposedContentOffsetCenterX = proposedContentOffset.x + collectionViewSize.width * 0.5
var proposedRect = collectionView!.bounds
// comment this out if you don't want it to scroll so quickly
proposedRect = CGRect(x: proposedContentOffset.x, y: 0, width: collectionViewSize.width, height: collectionViewSize.height)
var candidateAttributes: UICollectionViewLayoutAttributes?
for attributes in layoutAttributesForElements(in: proposedRect)! {
// == Skip comparison with non-cell items (headers and footers) == //
if attributes.representedElementCategory != .cell {
continue
}
// Get collectionView current scroll position
let currentOffset = collectionView!.contentOffset
// Don't even bother with items on opposite direction
// You'll get at least one, or else the fallback got your back
// swiftlint:disable:next line_length
if (attributes.center.x <= (currentOffset.x + collectionViewSize.width * 0.5) && velocity.x > 0) || (attributes.center.x >= (currentOffset.x + collectionViewSize.width * 0.5) && velocity.x < 0) {
continue
}
// First good item in the loop
if candidateAttributes == nil {
candidateAttributes = attributes
continue
}
// Save constants to improve readability
let lastCenterOffset = candidateAttributes!.center.x - proposedContentOffsetCenterX
let centerOffset = attributes.center.x - proposedContentOffsetCenterX
if fabsf(Float(centerOffset)) < fabsf(Float(lastCenterOffset)) {
candidateAttributes = attributes
}
}
if candidateAttributes != nil {
// Great, we have a candidate
let x = candidateAttributes!.center.x - collectionViewSize.width * 0.5
return CGPoint(x: x, y: proposedContentOffset.y)
} else {
// Fallback
let offset = proposedContentOffset
return super.targetContentOffset(forProposedContentOffset: offset)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment