简体   繁体   中英

UIAnimator's UISnapBehavior possible with UIScrollview?

I am trying to figure out if I can use a UISnapBehavior from UIAnimator inside a UIScrollview to make scroll view's content snap to a point. So far my findings have been lead to that this is impossible.

What I am Trying to Achieve

UIScrollView to 'snap' to certain point while the user is dragging the scroll view. However, the scrolling has to resume from snapped position without the user having to lift the touch.

Apple seems to achieve this in Photo Editing in their iOS Photos App. (See screenshot below)

在此处输入图片说明

What I Tried

I tried to gain this behavior by attaching a UIPanGestureRecognizer to the scrollview and using it's velocity. If the user is dragging towards the snapping point, scroll view will disable scrolling, animate to the snapping point, when done animating it will re-enable scrolling.

However, this lead to the issue where the user HAS TO LIFT the touch up after the drag and re-drag the scrollview. Yet, Apple seems to have done it without having to lift the drag.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM