The following snapping logic is for a collection with cells of the same size and one section, but the logic for more sections shouldn't be much different, or much more complex.

`scrollViewWillEndDragging`

has an `inout targetContentOffset`

parameter, meaning we can read and modify the end position of the scroll. Luckily, we don't need to take into consideration insets, line or item spacing (*I've lost a lot of time by including them, then not being able to understand why the correct math produces wrong results*):

```
let cellWidth = collectionView( // 1
collectionView,
layout: collectionView.collectionViewLayout,
sizeForItemAt: IndexPath(item: 0, section: 0)
).width
let page: CGFloat
let snapPoint: CGFloat = 0.3
let snapDelta: CGFloat = 1 - snapPoint
let proposedPage = targetContentOffset.pointee.x / max(1, cellWidth) // 2
if floor(proposedPage + snapDelta) == floor(proposedPage)
&& scrollView.contentOffset.x <= targetContentOffset.pointee.x { // 3
page = floor(proposedPage) // 4
}
else {
page = floor(proposedPage + 1) // 5
}
targetContentOffset.pointee = CGPoint( // 6
x: cellWidth * page,
y: targetContentOffset.pointee.y
)
```

First, we'll need our cell width (1) so we can calculate the "proposed page" (2): this is the "page" we're at during scrolling (for example `3.25`

would mean page `3`

and a quarter of the fourth). If our desired `snapPoint`

is `30%`

, then our `snapDelta`

would be `1 - 0.3 = 0.7`

. Think of it like this:

- if we have reached/passed
`30%`

of a page, then we virtually reached its end and we need to scroll to the beginning of the next one:`3.3 + 0.7 = 4.0`

and`floor(4.0) > floor(3.0)`

(5) - if we haven't reached
`30%`

of a page, then we need to stay on it by scrolling to its beginning:`3.25 + 0.7 = 3.95`

and`floor(3.95) == floor(3.0)`

(4)

We also need to consider the case where the user scrolls past the last page (3) - the `targetContentOffset`

will be within bounds, but the current `contentOffset`

won't be, so we need to check for that as well (5).

Finally, we replace the `targetContentOffset`

with our computed value (6).

Update, Feb 13, 2017: Based on the previous point of "insets don't have to be taken into account", I've lost a lot of time in a recent project, when I actually had to take them into consideration (for "true" pagination, at least), so I'm a bit lost about this.

If "true" pagination is desired, as in scroll one page at a time, we need to change things a little bit:

```
private var startingScrollingOffset = CGPoint.zero
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
startingScrollingOffset = scrollView.contentOffset // 1
}
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
// [...]
let offset = scrollView.contentOffset.x + scrollView.contentInset.left // 2
let proposedPage = offset / max(1, cellWidth)
let snapPoint: CGFloat = 0.1
let snapDelta: CGFloat = offset > startingScrollingOffset.x ? (1 - snapPoint) : snapPoint
if floor(proposedPage + snapDelta) == floor(proposedPage) { // 3
page = floor(proposedPage) // 4
}
else {
page = floor(proposedPage + 1) // 5
}
targetContentOffset.pointee = CGPoint(
x: cellWidth * page,
y: targetContentOffset.pointee.y
)
}
```

We'll now need to save the position when scrolling started (1). Then, we'll use the current `contentOffset`

and left inset instead of the `targetContentOffset`

(2). The `snapDelta`

logic changes too, as follows, based on direction:

swiping left:

- if we have reached/passed
`10%`

of the current page, then we virtually reached its end and we need to scroll to the next one:`3.1 + 0.9 = 4.0`

and`floor(4.0) > floor(3.0)`

(5) - if we haven't reached
`10%`

of the current page, we need to stay on it, by scrolling back to our starting point:`3.05 + 0.9 = 3.95`

and`floor(3.95) == floor(3.0)`

(4)

- if we have reached/passed
swiping right:

- if we have passed
`90%`

of the previous page, then we virtually passed its end and we need to scroll to its beginning:`2.89 + 0.1 = 2.99`

and`floor(2.99) < floor(3.0)`

(5) - if we haven't reached
`90%`

of the previous page, we need to stay on the current page, by scrolling back to our starting point:`2.91 + 0.1 = 3.01`

and`floor(3.01) == floor(3.0)`

(4)

- if we have passed

As for calculating our page: we'll remove the `targetContentOffset`

logic from the condition and use the `snapDelta`

we just computed (3) instead of a flat value of `0.7`

.

While the percentages were randomly picked, `0.1`

feels a bit better for true pagination, while `0.3`

feels better for snapped scrolling. If you have any feedback, drop by to chat @rolandleth.

Subscribe to my monthly newsletter.

No spam, unsubscribe at any time.

No spam, unsubscribe at any time.