Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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;
}
like image 507
Gizmodo Avatar asked Jan 03 '16 03:01

Gizmodo


2 Answers

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
}
like image 153
Warif Akhand Rishi Avatar answered Oct 17 '22 04:10

Warif Akhand Rishi


Subclass UIScrollView/UICollectionView

This solution does not require you lift your finger in order to unsnap and works while scrolling. If you need it for vertical scrolling and not horizontal scrolling just swap the x's with y's.

Set snapPoint to the content offset where you want the center of the snap to be.

Set snapOffset to the radius you want around the snapPoint for where snapping should occur.

If you need to know if the scrollView has snapped, just check the isSnapped variable.

class UIScrollViewSnapping : UIScrollView {
    public var snapPoint: CGPoint?
    public var snapOffset: CGFloat?
    public var isSnapped = false
    
    public override var contentOffset: CGPoint {
        set {
            if let snapPoint = self.snapPoint,
                let snapOffset = self.snapOffset,
                newValue.x > snapPoint.x - snapOffset,
                newValue.x < snapPoint.x + snapOffset {
                self.isSnapped = true
                super.contentOffset = snapPoint
            }
            else {
                self.isSnapped = false
                super.contentOffset = newValue
            }
        }
        get {
            return super.contentOffset
        }
    }
}
like image 3
Josh Bernfeld Avatar answered Oct 17 '22 04:10

Josh Bernfeld