Search code examples
iosswiftuiscrollviewdelegate

UIScrollViewDelegate - animateWithDuration in scrollViewDidEndDragging is not working as expected


I have a UIScrollView with 2 sub views/pages side by side (horizontal content size = 2 * Screen Width + gutter space between pages). I would like to increase the completion speed of the animation, ie after the user has completed the dragging and lifted the finger. Based on the suggestions found in SO, I implemented a UIScrollViewDelegate as below.

class MyScrollViewDelegate: NSObject, UIScrollViewDelegate
{
    var targetX: CGFloat = 0.0

    func scrollViewWillEndDragging(scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>)
    {
        // I have not implemented the paging logic yet as I wanted to test the replacement animation first.
        // Below code simply uses the suggested tagetOffset, but limiting the same between the valid min and max values

        targetX = fmax(0, fmin(scrollView.contentSize.width - scrollView.frame.size.width, targetContentOffset.memory.x))
    }

    func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool)
    {
        UIView.animateWithDuration(0.25, animations: { scrollView.contentOffset = CGPointMake(self.targetX, 0) })
    }
}

My understanding is that once the dragging is over, my own animation code in the scrollViewDidEndDragging will take over and complete the translation. The problem I am facing is that there seems to be an additional animation/jump when I pull the view inward when it is at the leftmost or rightmost edge.

For example, when the contentOffset.x = 0 (left edge), if I pull the view rightward by say 20 points and release (even after pausing for a second), the contentOffset.x & targetContentOffset.memory.x would be -20, and the calculated self.targetX would be 0. Hence in my animation I expect the page to move back towards left by 20 points with given speed as soon as I lift the finger. But what I observe is that, the page goes further to the right by almost the same amount (dragged distance) and then animates back from there to 0. The movement is so fast that I can't make out whether it is an animation or direct jump. Afterwards it follows my animation parameters to fall back.

As mentioned, the rightward jump seems to be proportional to the dragging distance, suggesting that my assumption about the "current position" before the start of animation is probably wrong. But I am not able to figure out the exact reason. Your help is much appreciated. The setup is ios 9.0, swift 2.0, xcode 7.0.1.

Edit 1:
I commented the animateWithDuration (but kept the scrollViewDidEndDragging). Animations stopped except in the edge region, where there is a default animation pulling the content back. Further reading pointed to the bounce property. I have a doubt that this default animation is colliding with the one I supplied.

Edit 2:
The bounce animation seems to be the culprit. While searching in this direction I came across another question in SO (Cancel UIScrollView bounce after dragging) describing the issue and possible solutions.


Solution

  • The issue seems to be because of queuing up of bounce animation and the custom animation. For details please read - Cancel UIScrollView bounce after dragging. I am not sure how the chosen answer solves the problem of custom duration. There is another solution involving sub-classing. That also didn't work in my case. Not sure whether it is due to different iOS versions.

    Following is what worked for me. It is only the basic snippet. You can enhance the same to have better animation curves and velocity handling.

    func scrollViewWillBeginDecelerating(scrollView: UIScrollView)
    {
        // below line seems to prevent the insertion of bounce animation
        scrollView.setContentOffset(scrollView.contentOffset, animated: false)
    
        // provide your animation with custom options
        UIView.animateWithDuration(0.25, animations: { scrollView.contentOffset = CGPointMake(targetX, targetY) })
    }