'Swift - UICollection snap back too fast if item scrolled a little

I'm trying to make UICollectionView snap back to the center of any item cell near to it, But the problem is when i move it slightly a little bit it snaps back too fast to the center of the cell which is not looking smooth, Is it possible to ignore or disable scroll unless the user is swiping to the next item and not make scrollview snaps it the same item ?

here's my code for snapping

func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {

    let layout = self.collectionView.collectionViewLayout as! UICollectionViewFlowLayout
    let cellWidthIncludingSpacing = layout.itemSize.width + layout.minimumLineSpacing
    var offset = targetContentOffset.pointee

    let index = (offset.x + scrollView.contentInset.left ) / cellWidthIncludingSpacing
    let roundedIndex = round(index)

    self.lastContentOffset = scrollView.contentOffset.x
    offset = CGPoint(x: roundedIndex * cellWidthIncludingSpacing - scrollView.contentInset.left, y: -scrollView.contentInset.top)

    targetContentOffset.pointee = offset


}

Edit: This is how it snaps back from physical device. it's here slower because it's gif

enter image description here



Solution 1:[1]

Its snapping back because you are directly setting the contentOffset. Instead you should call the UIScrollViewMethod setContentOffset(_:animated:) with animated being true. If you still do not like the results then consider instead implementing the UICollectionLayout method targetContentOffsetForProposedContentOffset:withScrollingVelocity: and then the layout will automatically snap to the point you return.

Solution 2:[2]

In case after 3 years someone's still facing this problem :)

For a fast workaround you can look at the velocity value in func scrollViewWillEndDragging. Empirically found that it snaps too fast if the velocity absolute value is less than 1.4 (in my case). So if it's less I set targetContentOffset.pointee to current offset and use scrollView.setContentOffset animated, if not – alter targetContentOffset.pointee.

Here is the code:

func scrollViewWillEndDragging(
    _ scrollView: UIScrollView,
    withVelocity velocity: CGPoint,
    targetContentOffset: UnsafeMutablePointer<CGPoint>
) {
    // Using Decimal to avoid calculation imprecisions
    let decItemWidthWithSpacing = Decimal(itemWidth + interItemSpacing)
    let decTargetOffset = Decimal(targetContentOffset.pointee.x)
    var pageIndexNotRounded = decTargetOffset / decItemWidthWithSpacing
    var pageIndex = Decimal()
    NSDecimalRound(&pageIndex, &pageIndexNotRounded, 0, .plain)

    let newTargetOffsetX = CGFloat(exactly: pageIndex * decItemWidthWithSpacing as NSNumber) ?? 0

    if abs(velocity.x) < 1.4 {
        targetContentOffset.pointee = scrollView.contentOffset
        scrollView.setContentOffset(CGPoint(x: newTargetOffsetX, y: 0), animated: true)
    } else {
        targetContentOffset.pointee.x = newTargetOffsetX
    }
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Josh Homann
Solution 2 Masha Trubina