Search code examples
iosswiftpaginationuiscrollviewscroll-snap-points

UIScrollView snap-to-position while scrolling


I am trying to implement a scroll view that snaps to points while scrolling.

All the posts here I've seen about snapping to a point 'after' the user has ended dragging the scroll. I want to make it snap during dragging.

So far I have this to stop the inertia after dragging and it works fine:

func scrollViewWillEndDragging(scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
      targetContentOffset.memory = scrollView.contentOffset
}

I tried this but not working as desired:

    var scrollSnapHeight : CGFloat = myScrollView.contentSize.height/10

scrollViewDidScroll:

func scrollViewDidScroll(scrollView: UIScrollView) {
    let remainder : CGFloat = scrollView.contentOffset.y % scrollSnapHeight
    var scrollPoint : CGPoint = scrollView.contentOffset
    if remainder != 0 && scrollView.dragging
    {
        if self.lastOffset > scrollView.contentOffset.y //Scrolling Down
        {
            scrollPoint.y += (scrollSnapHeight - remainder)
            NSLog("scrollDown")
        }
        else //Scrolling Up
        {
            scrollPoint.y -= (scrollSnapHeight - remainder)
        }
        scrollView .setContentOffset(scrollPoint, animated: true)
    }
    self.lastOffset = scrollView.contentOffset.y;
}

Solution

  • This approach is going to enable / disable scrollEnabled property of UIScrollView.

    When scrollView scrolls outside the given scrollSnapHeight, make scrollEnabled to false. That will stop the scrolling. Then make scrolling enable again for the next drag.

    extension ViewController: UIScrollViewDelegate {
    
        func scrollViewDidScroll(scrollView: UIScrollView) {
    
            if scrollView.contentOffset.y > lastOffset + scrollSnapHeight {
                scrollView.scrollEnabled = false
            } else if scrollView.contentOffset.y < lastOffset - scrollSnapHeight {
                scrollView.scrollEnabled = false
            }
        }
    
        func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool) {
    
            guard !decelerate else {
                return
            }
    
            setContentOffset(scrollView)
        }
    
        func scrollViewWillBeginDecelerating(scrollView: UIScrollView) {
    
            setContentOffset(scrollView)
        }
    }
    

    func setContentOffset(scrollView: UIScrollView) {
    
        let stopOver = scrollSnapHeight
        var y = round(scrollView.contentOffset.y / stopOver) * stopOver
        y = max(0, min(y, scrollView.contentSize.height - scrollView.frame.height))
        lastOffset = y
    
        scrollView.setContentOffset(CGPointMake(scrollView.contentOffset.x, y), animated: true)
    
        scrollView.scrollEnabled = true
    }