UIView animation based on UIPanGestureRecognizer velocity
Asked Answered
R

3

43

I would like to be able to move a subview on and off the screen much like you browse between images in the iPhone's build in Photos app, so if the subview is more than 1/2 off screen when I let go with my finger it must animate off screen, but it must also support swipe so if the swipe/pan velocity is high enough it must animate off screen even though is might be less than 1/2 off screen.

My idea was to use UIPanGestureRecognizer and then test on the velocity. This works, but how do I set a correct animation duration for the moving of the UIView based on the view's current location and velocity of the pan so that it seems smooth? If I set a fixed value, the animation either starts to slow or to fast compared to my fingers swipe speed.

Regurgitation answered 14/11, 2011 at 12:5 Comment(0)
F
59

The docs say

The velocity of the pan gesture, which is expressed in points per second. The velocity is broken into horizontal and vertical components.

So I'd say, given you want to move your view xPoints (measured in pt) to let it go off-screen, you could calculate the duration for that movement like so:

CGFloat xPoints = 320.0;
CGFloat velocityX = [panRecognizer velocityInView:aView].x;
NSTimeInterval duration = xPoints / velocityX;
CGPoint offScreenCenter = moveView.center;
offScreenCenter.x += xPoints;
[UIView animateWithDuration:duration animations:^{
    moveView.center = offScreenCenter;
}];

You might want to use + (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion instead though and try out different UIViewAnimationOptions.

Firenze answered 15/11, 2011 at 18:1 Comment(0)
A
24

An observation on relying upon the velocity in UIPanGestureRecognizer: I don't know about your experience, but I found the system generated velocity on the simulator to be not terribly useful. (It's ok on the device, but problematic on simulator.)

If you pan quickly across and abruptly stop, wait, and only then end the gesture (e.g. user starts a swipe, realizes that this wasn't what they wanted, so they stop and then release their finger), the velocity reported by velocityInView: at the UIGestureRecognizerStateEnded state when your finger was released seems to be the fast speed before I stopped and waited, whereas the right velocity in this example would be zero (or near zero). In short, the reported velocity is that which it was right before the end of the pan, but not the speed at the end of the pan itself.

I ended up calculating the velocity myself, manually. (It seems silly that this is necessary, but I didn't see any way around it if I really wanted to get the final speed of the pan.) Bottom line, when the state is UIGestureRecognizerStateChanged I keep track of the current and previous translationInView CGPoint as well as the time, and then using those values when I was in the UIGestureRecognizerStateEnded to calculate the actual final velocity. It works pretty well.

Here is my code for calculating the velocity. I happen to not be using velocity to figure out the speed of the animation, but rather I'm using it to determine whether the user either panned far enough or flicked quickly enough for the view to move more than half way across the screen and thus triggering the animation between views, but the concept of calculating the final velocity seems applicable to this question. Here's the code:

- (void)handlePanGesture:(UIPanGestureRecognizer *)gesture
{
    static CGPoint lastTranslate;   // the last value
    static CGPoint prevTranslate;   // the value before that one
    static NSTimeInterval lastTime;
    static NSTimeInterval prevTime;

    CGPoint translate = [gesture translationInView:self.view];

    if (gesture.state == UIGestureRecognizerStateBegan)
    {
        lastTime = [NSDate timeIntervalSinceReferenceDate];
        lastTranslate = translate;
        prevTime = lastTime;
        prevTranslate = lastTranslate;
    }
    else if (gesture.state == UIGestureRecognizerStateChanged)
    {
        prevTime = lastTime;
        prevTranslate = lastTranslate;
        lastTime = [NSDate timeIntervalSinceReferenceDate];
        lastTranslate = translate;

        [self moveSubviewsBy:translate];
    }
    else if (gesture.state == UIGestureRecognizerStateEnded)
    {
        CGPoint swipeVelocity = CGPointZero;

        NSTimeInterval seconds = [NSDate timeIntervalSinceReferenceDate] - prevTime;
        if (seconds)
        {
            swipeVelocity = CGPointMake((translate.x - prevTranslate.x) / seconds, (translate.y - prevTranslate.y) / seconds);
        }

        float inertiaSeconds = 1.0;  // let's calculate where that flick would take us this far in the future
        CGPoint final = CGPointMake(translate.x + swipeVelocity.x * inertiaSeconds, translate.y + swipeVelocity.y * inertiaSeconds);

        [self animateSubviewsUsing:final];
    }
}
Amaze answered 2/5, 2012 at 16:33 Comment(6)
It does not work in iOS 6 because the final translation and the lastTranslation is the same in UIGestureRecognizerStateEnded so the final velocity is always 0.Peursem
Thanks, I see. It is an issue of simulator. I fired a bug report to Apple.Peursem
I've modified the source (changing a few variable names) to hopefully avoid this confusion. This code worked fine as it was, but hopefully the new variable names make it even a little less confusing. Thanks for reporting the bug to Apple.Amaze
"translate" is always (0,0) on UIGestureRecognizerStateEnded state. to calculate swipeVelocity your probably want to subtract (lastTranslate - prevTranslate) insteadPatrolman
Just wan to point out that using static variables will make the values persistant within the app lifecycle, even in another object. Think about it before using them.Pol
Awesome answer. Would be great to have this in swift :)Issus
C
3

In most cases setting UIViewAnimationOptionBeginFromCurrentState option for UIView animator is enough to make a flawlessly continued animation.

Chemurgy answered 31/8, 2014 at 16:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.