UIAnimator UISnapBehavior possible with UIScrollview?

I'm trying to figure out if I can use the UISnapBehavior from the UIAnimator inside the UIScrollview to bind the scroll content to a point. So far, my findings have made it impossible.

What am i trying to achieve

UIScrollView "snap" to a specific point while the user drags the scroll. However, the scroll should resume from the snap position if the user does not have to raise the touch.

Apple seems to be doing this in Photo Editing in its iOS Photos app. (See screenshot below)

enter image description here

What i tried

I tried to get this behavior by attaching a UIPanGestureRecognizer to a scrollview and using its speed. If the user is dragged to the anchor point, viewing the scroll will disable scrolling, revive the anchor point, when the animation is completed, it will enable scrolling again.

However, this results in the user having to RAISE the touch up after dragging and dragging the scroll. However, Apple seems to have done this without removing resistance.

+2
source share
2 answers

I tried to simulate the 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 type of scroll requires 3 steps.

 enum SnapState { case willSnap case didSnap case willRelease } 
  • willSnap: The default state. Decide when to click. Compare contentOffset distance from SnapPoint with minDistanceToSnap and scrollview velocity with minVelocityToSnap . Switch to didSnap state.
  • didSnap: Manually setContentOffset to the provided contextOffset(snapPoint) . Compute dragDistance on scrollView . If the user has dragged more than a certain distance ( minDragDistanceToReleaseSnap ) to willRelease state.
  • willRelease: Replace the willSnap value if the distance scroll from snapPoint greater 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. The project was completed using Xcode 7.2. You may need to change the bit to compile.

+4
source

Do not add the UIPanGestureRecognizer directly to the UIScrollView. Rather, add it to the container view, then manually select the UIScrollView contentOffset in the selector.

Disable interaction directly with the UIScrollView or use a delegate to prevent direct interaction with the scroll view.

+1
source

All Articles