UIAnimator's UISnapBehavior possible with UIScrollview?

旧街凉风 提交于 2019-12-06 05:38:12

I've tried to mimic iOS Photos app. Here is my logic:

// CALCULATE A CONTENT OFFSET FOR SNAPPING POINT 
let snapPoint = CGPoint(x: 367, y: 0)  

// CHANGE THESE VALUES TO TEST
let minDistanceToSnap = 7.0
let minVelocityToSnap = 25.0
let minDragDistanceToReleaseSnap = 7.0
let snapDuringDecelerating = false

This kind of scrolling needs 3 stages

enum SnapState {
case willSnap
case didSnap
case willRelease
}
  1. willSnap: Default state. Decide when to snap. Compare contentOffset distance from SnapPoint with minDistanceToSnap and scrollview velocity with minVelocityToSnap. Change to didSnap state.
  2. didSnap: Manually setContentOffset to a provided contextOffset(snapPoint). Calculate dragDistance on scrollView. If user drag more than a certain distance (minDragDistanceToReleaseSnap) change to willRelease state.
  3. willRelease: Change to willSnap state again if distance scroll from snapPoint is more than minDistanceToSnap.


extension ViewController: UIScrollViewDelegate {
    func scrollViewDidScroll(scrollView: UIScrollView) {
        switch(snapState) {
            case .willSnap:
                let distanceFromSnapPoint = distance(between: scrollView.contentOffset, and: snapPoint)
                let velocity = scrollView.panGestureRecognizer.velocityInView(view)
                let velocityDistance = distance(between: velocity, and: CGPointZero)
                if distanceFromSnapPoint <= minDistanceToSnap && velocityDistance <= minVelocityToSnap && (snapDuringDecelerating || velocityDistance > 0.0) {
                    startSnapLocaion = scrollView.panGestureRecognizer.locationInView(scrollView)
                    snapState = .didSnap
                }
            case .didSnap:
                scrollView.setContentOffset(snapPoint, animated: false)
                var dragDistance = 0.0
                let location = scrollView.panGestureRecognizer.locationInView(scrollView)
                dragDistance = distance(between: location, and: startSnapLocaion)
                if dragDistance > minDragDistanceToReleaseSnap  {
                    startSnapLocaion = CGPointZero
                    snapState = .willRelease
                }
            case .willRelease:
                let distanceFromSnapPoint = distance(between: scrollView.contentOffset, and: snapPoint)
                if distanceFromSnapPoint > minDistanceToSnap {
                    snapState = .willSnap
                }
        }
    }
}

Helper function

func distance(between point1: CGPoint, and point2: CGPoint) -> Double {
    return Double(hypotf(Float(point1.x - point2.x), Float(point1.y - point2.y)))
}

Made a demo project on Github: https://github.com/rishi420/SnapDrag

Note: Project made with Xcode 7.2. You may need to change a bit to compile.

Don't add the UIPanGestureRecognizer directly to the UIScrollView. Rather add it to a container view, then in the selector, set the UIScrollView contentOffset manually.

Disable interaction on the UIScrollView itself, or use the delegate, to prevent interaction directly with the scroll view.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!