How do I capture the point initially tapped in a UIPanGestureRecognizer?

16,014

Solution 1

Late to the party, but I notice that nothing above actually answers the question, and there is in fact a way to do this. You must subclass UIPanGestureRecognizer and include:

#import <UIKit/UIGestureRecognizerSubclass.h>

either in the Objective-C file in which you write the class or in your Swift bridging header. This will allow you to override the touchesBegan:withEvent method as follows:

class SomeCoolPanGestureRecognizer: UIPanGestureRecognizer {
    private var initialTouchLocation: CGPoint!

    override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent) {
        super.touchesBegan(touches, withEvent: event)
        initialTouchLocation = touches.first!.locationInView(view)
    }
}

Then your property initialTouchLocation will contain the information you seek. Of course in my example I make the assumption that the first touch in the set of touches is the one of interest, which makes sense if you have a maximumNumberOfTouches of 1. You may want to use more sophistication in finding the touch of interest.

Edit: Swift 5


import UIKit.UIGestureRecognizerSubclass

class InitialPanGestureRecognizer: UIPanGestureRecognizer {
  private var initialTouchLocation: CGPoint!

  override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
    super.touchesBegan(touches, with: event)
    initialTouchLocation = touches.first!.location(in: view)
  }
}

Solution 2

You should be able to use translationInView: to calculate the starting location unless you reset it in between. Get the translation and the current location of touch and use it to find the starting point of the touch.

Solution 3

@John Lawrence has it right.

Updated for Swift 3:

import UIKit.UIGestureRecognizerSubclass

class PanRecognizerWithInitialTouch : UIPanGestureRecognizer {
  var initialTouchLocation: CGPoint!
  
  override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
    super.touchesBegan(touches, with: event)
    initialTouchLocation = touches.first!.location(in: view)
  }
}

Note that the instance variable initialTouchLocation cannot be private, if you want to access it from your subclass instance (handler).

Now in the handler,

  func handlePan (_ sender: PanRecognizerWithInitialTouch) {
    let pos = sender.location(in: view)
    
    switch (sender.state) {
    case UIGestureRecognizerState.began:
      print("Pan Start at \(sender.initialTouchLocation)")
      
    case UIGestureRecognizerState.changed:
      print("    Move to \(pos)")

Solution 4

You could use this method:

CGPoint point    = [gesture locationInView:self.view];

Solution 5

in the same UIView put in this method.

//-----------------------------------------------------------------------
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    CGPoint point = [[[event allTouches] anyObject] locationInView:self];
NSLog(@"point.x ,point.y  : %f, %f",point.x ,point.y);
}

look for it in the UIGestureRecognizer Class Reference here: https://developer.apple.com/library/ios/documentation/uikit/reference/UIGestureRecognizer_Class/Reference/Reference.html

Share:
16,014

Related videos on Youtube

todd412
Author by

todd412

iOS/MacOS/tvOS Developer.

Updated on March 09, 2020

Comments

  • todd412
    todd412 about 4 years

    I have an app that lets the user trace lines on the screen. I am doing so by recording the points within a UIPanGestureRecognizer:

    -(void)handlePanFrom:(UIPanGestureRecognizer *)recognizer
    {
        CGPoint pixelPos = [recognizer locationInView:rootViewController.glView];
        NSLog(@"recorded point %f,%f",pixelPos.x,pixelPos.y);
    }
    

    That works fine. However, I'm very interested in the first point the user tapped before they began panning. But the code above only gives me the points that occurred after the gesture was recognized as a pan (vs. a tap.)

    From the documentation, it appears there may be no easy way to determine the initially-tapped location within the UIPanGestureRecognizer API. Although within UIPanGestureRecognizer.h, I found this declaration:

    CGPoint _firstScreenLocation;
    

    ...which appears to be private, so no luck. I'm considering going outside the UIGestureRecognizer system completely just to capture that initailly-tapped point, and later refer back to it once I know that the user has indeed begun a UIPanGesture. I Thought I would ask here, though, before going down that road.

    • Milos
      Milos over 9 years
      The number of omitted points can be much larger than one. The recogniser has to rule out incidental movement, e.g. on touch and hold... So, the slower (less confident/definitive) the touches the more points it will take before getting the first locationInView: from the pan recogniser. For example I recorded the following sequence: touches began at {115, 739}, touches moved at {116, 739}, touches moved at {117, 739}, touches moved at {120, 740}, touches moved at {125, 742}, location in view: {125, 742}
    • Milos
      Milos over 9 years
      Just to add that combining touchesBegan: and touchesMoved: with a gesture recogniser is not a bad thing: You can start giving the user visual feedback as soon as touchesBegan: but not commit to anything until UIGestureRecognizerStateBegan...
  • todd412
    todd412 over 12 years
    Unfortunately, that actually isn't the case. I should have been more specific in my initial explanation: I actually use UIGestureRecognizerStateBegan within a case-switch statement to detect the beginning of the pan gesture. But the locationInView detected within that state block is the first point >after< the pan began, not the initial point tapped.
  • Milos
    Milos over 9 years
    Unfortunately, translationInView: returns {0, 0} in the state UIGestureRecognizerStateBegan. This means that this approach yields the same end result as when using just locationInView:
  • Warpling
    Warpling almost 9 years
    This should be the selected answer.
  • Warpling
    Warpling almost 9 years
    @milos it is different. translationInView will return {0, 0} if the gesture wasn't fast enough to caused some translation before it was recognized.
  • Warpling
    Warpling almost 9 years
    See @john-lawrence's answer below for an extensible solution.
  • Warpling
    Warpling almost 9 years
    This provides the current location of the pan gesture, not the initial point.
  • Chris Garrett
    Chris Garrett over 8 years
    In Swift, instead of using the bridging header you can also add the following line to your subclass file: import UIKit.UIGestureRecognizerSubclass
  • Andrew Rasmussen
    Andrew Rasmussen almost 8 years
    Yeah translationInView got me closer to the initial touch, but still not quite correct. I think subclassing UIPanGestureRecognizer is the way to go.
  • Brent Faust
    Brent Faust almost 7 years
    It may be counter-intuitive, but this doesn't work, since the initial location passed to the UIPanGestureRecognizer's handler (in the Began state) is incorrect (shifted by 12 pixels or so, and thus the reason for the OP's question).