Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UIView animation with UIPanGestureRecognizer velocity way too fast (not decelerating)

Update: Though I'd still like to solve this, I ended up switching to animateWithDuration:delay:options:animations:completion: and it works much nicer. It's missing that nice "bounce" at the end that the spring affords, but at least it's controllable.


I am trying to create a nice gesture-driven UI for iOS but am running into some difficulties getting the values to result in a nice natural feeling app.

I am using animateWithDuration:delay:usingSpringWithDamping:initialSpringVelocity:options:animations:completion: because I like the bouncy spring animation. I am initializing the velocity argument with the velocity as given by the gesture recognizer in the completed state. The problem is if I pan quickly enough and let go, the velocity is in the thousands, and my view ends up flying right off the screen and then bouncing back and forth with such dizzying vengeance.

I'm even adjusting the duration of the animation relative to the amount of distance the view needs to move, so that if there are only a few pixels needed, the animation will take less time. That, however, didn't solve the issue. It still ends up going nuts.

What I want to happen is the view should start out at whatever velocity the user is dragging it at, but it should quickly decelerate when reaching the target point and only bounce a little bit at the end (as it does if the velocity is something reasonable).

I wonder if I am using this method or the values correctly. Here is some code to show what I'm doing. Any help would be appreciated!

- (void)handlePanGesture:(UIPanGestureRecognizer*)gesture {
    CGPoint offset = [gesture translationInView:self.view];
    CGPoint velocity = [gesture velocityInView:self.view];

    NSLog(@"pan gesture state: %d, offset: %f velocity: %f", gesture.state, offset.x, velocity.x);
    static CGFloat initialX = 0;

    switch ( gesture.state ) {
        case UIGestureRecognizerStateBegan: {
            initialX = self.blurView.x;
        break; }

        case UIGestureRecognizerStateChanged: {
            self.blurView.x = initialX + offset.x;
        break; }

        default:
        case UIGestureRecognizerStateCancelled:
        case UIGestureRecognizerStateEnded: {
            if ( velocity.x > 0 )
                [self openMenuWithVelocity:velocity.x];
            else
                [self closeMenuWithVelocity:velocity.x];
        break; }
    }
}

- (void)openMenuWithVelocity:(CGFloat)velocity {
    if ( velocity < 0 )
        velocity = 1.5f;

    CGFloat distance = -40 - self.blurView.x;
    CGFloat distanceRatio = distance / 260;
    NSLog(@"distance: %f ratio: %f", distance, distanceRatio);

    [UIView animateWithDuration:(0.9f * distanceRatio) delay:0 usingSpringWithDamping:0.7 initialSpringVelocity:velocity options:UIViewAnimationOptionBeginFromCurrentState animations:^{
        self.blurView.x = -40;
    } completion:^(BOOL finished) {
        self.isMenuOpen = YES;
    }];
}
like image 206
devios1 Avatar asked Oct 06 '13 03:10

devios1


2 Answers

First you need to calculate the remaining distance that the animation will have to take care of. When you have the delta distance you can proceed to calculate the velocity like this:

CGFloat springVelocity = fabs(gestureRecognizerVelocity / distanceToAnimate);

For clean velocity transfer you must use UIViewAnimationOptionCurveLinear.

like image 111
Maciej Swic Avatar answered Nov 14 '22 14:11

Maciej Swic


Came across this post while looking for a solution to a related issue. The problem is, you're passing in the velocity from UIPanGestureRecognizer, which is in points/second, when - animateWithDuration:delay:usingSpringWithDamping:initialSpringVelocity:options:animations:completion wants… a slightly odder value:

The initial spring velocity. For smooth start to the animation, match this value to the view’s velocity as it was prior to attachment. A value of 1 corresponds to the total animation distance traversed in one second. For example, if the total animation distance is 200 points and you want the start of the animation to match a view velocity of 100 pt/s, use a value of 0.5.

The animation method wants velocity in "distances" per second, not points per second. So, the value you should be passing in is (velocity from the gesture recognizer) / (total distance traveled during the animation).

That said, that's exactly what I'm doing and there's still a slight, yet noticeable "hiccup" between when the gesture recognizer is moving it, and when the animation picks up. That said, it should still work a lot better than what you had before. 😃

like image 29
Cocoatype Avatar answered Nov 14 '22 14:11

Cocoatype