How to determine true end velocity of pan gesture?
Asked Answered
S

2

5

When using UIPanGestureRecognizer and detecting UIGestureRecognizerStateEnded, then the velocity of the gesture is not the true velocity. Instead, it's the old velocity of the previous invocation of my action method. How can I access the true velocity at the end of the gesture?

I create my UIPanGestureRecognizer like this:

    UIPanGestureRecognizer* panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGestureRecognized:)];
    [panGestureRecognizer setMaximumNumberOfTouches:2];
    [panGestureRecognizer setMinimumNumberOfTouches:1];
    [panGestureRecognizer setDelegate:self];
    [panGestureRecognizer setDelaysTouchesBegan:NO];
    [panGestureRecognizer setDelaysTouchesEnded:NO];
    [panGestureRecognizer setCancelsTouchesInView:NO];
    [self addGestureRecognizer:panGestureRecognizer];

The beginning of my action method is here:

- (IBAction) panGestureRecognized:(UIPanGestureRecognizer *)recognizer {

    UIGestureRecognizerState state = recognizer.state;

    CGPoint gestureTranslation = [recognizer translationInView:self];
    CGPoint gestureVelocity = [recognizer velocityInView:self];

    [CBAppDelegate log:@"panGestureRecognized: state: %s\n    translation: (%f, %f)\n    velocity: (%f, %f)", [self toString:state], gestureTranslation.x, gestureTranslation.y, gestureVelocity.x, gestureVelocity.y];

Example of the log output:

2013-09-30_10:46:32.830 panGestureRecognized: state: UIGestureRecognizerStateChanged
    translation: (-283.000000, 2.000000)
    velocity: (-43.046783, 45.551472)
2013-09-30_10:47:02.942 panGestureRecognized: state: UIGestureRecognizerStateEnded
    translation: (-283.000000, 2.000000)
    velocity: (-43.046783, 45.551472)

As you can see, the velocity is the same in both log entries (same story with translation, but I care only about velocity), although I was holding down my finger for about 30 seconds without moving it, and then lifting the finger. You can tell the timing from the timestampts of the entries. There should certainly not be a velocity reported after 30 seconds of not moving my finger.

I've tested this with the iOS simulator for iPhone with iOS 6.1.

Shinbone answered 30/9, 2013 at 10:51 Comment(2)
You can calculate that yourself with timestamps. For a long holds, you can reset the starting timestamp if starting timestamp was long enough, and calculate the whatever however you want. Good Luck!Gama
It's absolutely amazing nobody has addressed this question in five years! I've put in the better, more correct, more modern answer .. 2019Alysaalyse
R
19

The velocityInView method is defined only when a pan occurs. That is, only when you're actually moving the finger a pan gesture is occurring. If you keep your finger still, it does not actually trigger a pan gesture.

This means that there is no built-in method to know the movement speed when the gesture ends. You could do something like check the time difference between the last event with the status value as UIGestureRecognizerStateChanged and UIGestureRecognizerStateEnded. You can then tune this threshold in order to obtain the desired behavior.

For example

- (IBAction) panGestureRecognized:(UIPanGestureRecognizer *)recognizer {

    UIGestureRecognizerState state = recognizer.state;

    CGPoint gestureTranslation = [recognizer translationInView:self];
    CGPoint gestureVelocity = [recognizer velocityInView:self];

    if ( state == UIGestureRecognizerStateChanged )
         _lastChange = CFAbsoluteTimeGetCurrent();
    else if ( state == UIGestureRecognizerStateEnded ) {
         double curTime = CFAbsoluteTimeGetCurrent(); 
         double timeElapsed = curTime - _lastChange;
         if ( timeElapsed < MY_THRESHOLD )
              finalSpeed = gestureVelocity;
         else
              finalSpeed = CGPointZero;
    }   
 }
Rodgers answered 30/9, 2013 at 11:8 Comment(4)
Great Answer !! can i know what is value you set for MY_THRESHOLD?Shulins
I'm afraid I only created this toy example for the question, i did not actually have to use it myself! You could maybe play with different values and see which one works bestRodgers
Hey, how did you link your uitableview (or I guess its superclass, uiscrollview) with the pangesture IBOutletOringa
This is really only a rough solution. I've put in the only way (I know of) to actually do it :/Alysaalyse
A
1

2019 how-to ...

This is the only way to really know the velocity when the finger comes up:

have some variables...

var cat: CADisplayLink? = nil
var prevTime = CFAbsoluteTimeGetCurrent()
var lastKnownPosition: CGFloat = 0
var lastKnownActualVelocity: Double = 0

and then ...

@objc func _checkVelocityEveryTrueFrame() {
    let newTime = CFAbsoluteTimeGetCurrent()
    let frameTime = newTime - prevTime
    prevTime = newTime

    let newPos = yourConstraint.constant
    lastKnownActualVelocity = Double(newPos - lastKnownPosition) / frameTime
    lastKnownPosition = newPos
    print("- \(frameTime) \(lastKnownPosition) \(lastKnownActualVelocity)")
}

@objc func dragOrFlick(_ p: UIPanGestureRecognizer) {
    if p.state == .began {
        cat?.invalidate()
        cat = nil
        cat = CADisplayLink(target: self,
             selector: #selector(_checkVelocityEveryTrueFrame))
        cat?.add(to: .main, forMode: .common)
    }

    if p.state == .changed {
        ... p.translation(in: ...
        yourConstraint.constant = new position...
    }

    if p.state == .ended {
        cat?.invalidate()
        cat = nil
        let trueFinalVelocity = lastKnownActualVelocity
        print("trueFinalVelocity is truly \(trueFinalVelocity)")
    }
}

That's it. As far as I know there's no simpler way.


+ Footnote. As any game programmer will tell you, even this is a bit shoddy; it gives the platonic velocity over one frame: purists would smooth it a little over a discussable amount of frames :/ It's a tricky issue.

Alysaalyse answered 6/8, 2019 at 19:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.