Created
June 14, 2018 14:46
-
-
Save romanfurman6/e907ccaed89976f8591b9cf736108619 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 CollectionViewFlowLayout: UICollectionViewFlowLayout { | |
private let sideItemScale: CGFloat = 0.9 | |
private let sideItemAlpha: CGFloat = 0.9 | |
private let sideItemShift: CGFloat = 0.0 | |
var visibleOffset: CGFloat! | |
override open func prepare() { | |
super.prepare() | |
self.updateLayout() | |
} | |
private func updateLayout() { | |
guard let collectionView = collectionView else { return } | |
let collectionSize = collectionView.bounds.size | |
let yInset = (collectionSize.height - self.itemSize.height) / 2 | |
let xInset = (collectionSize.width - self.itemSize.width) / 2 | |
self.sectionInset = UIEdgeInsets(top: yInset, left: xInset, bottom: yInset, right: xInset) | |
let side = self.itemSize.width | |
let scaledItemOffset = (side - side * self.sideItemScale) / 2 | |
let fullSizeSideItemOverlap = visibleOffset + 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({ self.transformLayoutAttributes($0) }) | |
} | |
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.alpha = alpha | |
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 self.layoutAttributesForElements(in: proposedRect)! { | |
// == Skip comparison with non-cell items (headers and footers) == // | |
if attributes.representedElementCategory != .cell { | |
continue | |
} | |
// Get collectionView current scroll position | |
let currentOffset = self.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