UIScrollView snap-to-position while scrolling

2019-04-25 07:35发布

问题:

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;
}

回答1:

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
}


回答2:

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
        }
    }
}