Created
May 18, 2018 14:09
-
-
Save pddkhanh/94a6ec15da9baacfad8da88cd7288a3a to your computer and use it in GitHub Desktop.
Snaping CollectionViewFlowLayout like AppStore
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
/** | |
Support snaping to the left as AppStore layout | |
*/ | |
class SnapingCollectionViewFlowLayout: UICollectionViewFlowLayout { | |
override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, | |
withScrollingVelocity velocity: CGPoint) -> CGPoint { | |
guard let collectionView = collectionView else { | |
return super.targetContentOffset(forProposedContentOffset: proposedContentOffset, | |
withScrollingVelocity: velocity) | |
} | |
var offsetAdjustment = CGFloat.greatestFiniteMagnitude | |
let horizontalOffset = proposedContentOffset.x + collectionView.contentInset.left | |
let targetRect = CGRect(x: proposedContentOffset.x, y: 0, width: collectionView.bounds.size.width, | |
height: collectionView.bounds.size.height) | |
let layoutAttributesArray = super.layoutAttributesForElements(in: targetRect) | |
// Looking for the item that has most visible area (percentage) and scroll to that item | |
let contentOffset = collectionView.contentOffset | |
var selectedAttributes: UICollectionViewLayoutAttributes? | |
var selectedAttributesPct: CGFloat? | |
layoutAttributesArray?.forEach({ (layoutAttributes) in | |
// Avoid zero divine | |
if targetRect.width > 0 && layoutAttributes.frame.width > 0 { | |
// Calculate the percentage of visible area (width) | |
let intersectPct = layoutAttributes.frame.intersection(targetRect).width / layoutAttributes.frame.width | |
// Then find the item that hast most visible area | |
if selectedAttributesPct == nil || selectedAttributesPct! < intersectPct { | |
// We need to care about the velocity, if user is scrolling to the right, we will not want to snap | |
// to the left item, and vice versa. | |
if (velocity.x > 0 && layoutAttributes.frame.minX > contentOffset.x) | |
|| (velocity.x < 0 && layoutAttributes.frame.minX < contentOffset.x) | |
|| velocity.x == 0 { | |
selectedAttributes = layoutAttributes | |
selectedAttributesPct = intersectPct | |
} | |
} | |
} | |
}) | |
// Snap to that item if found | |
if let attributes = selectedAttributes { | |
return CGPoint(x: attributes.frame.minX - sectionInset.left, y: proposedContentOffset.y) | |
} | |
// Fallback. In case above logic not cover enough | |
return super.targetContentOffset(forProposedContentOffset: proposedContentOffset, | |
withScrollingVelocity: velocity) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment