Created
July 26, 2018 17:02
-
-
Save romanfurman6/9e575d11b2026199785bad412eaa1bf7 to your computer and use it in GitHub Desktop.
This file contains 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
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