Skip to content

Instantly share code, notes, and snippets.

@pddkhanh
Created May 18, 2018 14:09
Show Gist options
  • Save pddkhanh/94a6ec15da9baacfad8da88cd7288a3a to your computer and use it in GitHub Desktop.
Save pddkhanh/94a6ec15da9baacfad8da88cd7288a3a to your computer and use it in GitHub Desktop.
Snaping CollectionViewFlowLayout like AppStore
/**
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