简体   繁体   中英

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

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.

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)
  }
}

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.

@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)")

你可以使用这个方法:

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

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

I have also noticed that when I attempt to read the value in the shouldBegin method on a UIPanGestureRecognizer, I only see its location after the user moved a little bit (ie when the gesture is beginning to recognize a pan). It would be very useful to know where this pan gesture actually started though so that I can decide if it should recognize or not.

If you don't want to subclass UIGestureRecognizer view, you have two options:

  1. UILongPressGestureRecognizer , and set delay to 0
  2. UIPanGestureRecognizer , and capture start point in shouldReceiveTouch

If you have other gestures (eg tap, double tap, etc), then you'll likely want option 2 because the long press gesture recognizer with delay of 0 will cause other gestures to not be recognized properly.

If you don't care about other gestures, and only want the pan to work properly, then you could use a UILongPressGestureRecognizer with a 0 delay and it'll be easier to maintain cause you don't need to manually keep track of a start point.


Solution 1: UILongPressGestureRecognizer

Good for: simplicity

Bad for: playing nice with other gesture handlers

When creating the gesture, make sure to set minimumPressDuration to 0 . This will ensure that all your delegate methods (eg should begin) will receive the first touch properly.

Because a UILongPressGestureRecognizer is a continuous gesture recognizer (as opposed to a discrete gesture recognizer), you can handle movement by handling the UIGestureRecognizer.State.changed property just as you would with a UIPanGestureRecognizer (which is also a continuous gesture recognizer). Essentially you're combining the two gestures .

let gestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(gestureHandler(_:))
gestureRecognizer.minimumPressDuration = 0

Solution 2: UIPanGestureRecognizer

Good for: Playing nicely with other gesture recognizers

Bad for: Takes a little more effort saving the start state

The steps:

  1. First, you'll need to register as the delegate and listen for the shouldReceiveTouch event.

  2. Once that happens, save the touch point in some variable (not the gesture point!).

  3. When it comes time to decide if you actually want to start the gesture, read this variable.

var gestureStartPoint: CGPoint? = nil

// ...

let gestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(gestureHandler(_:))
gestureRecognizer.delegate = self

// ...

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
    gestureStartPoint = touch.location(in: self)
    return true
}

Warning: Make sure to read touch.location(in: self) rather than gestureRecognizer.location(in: self) as the former is the only way to get the start position accurately.

You can now use gestureStartPoint anywhere you want, such as should begin:

func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
    return isValidStartPoint(gestureStartPoint!)
}

You can use UIGestureRecognizerStateBegan method. Here is the link to Apple documentation on UIGestureRecognizer class.

http://developer.apple.com/library/ios/ipad/#documentation/uikit/reference/UIGestureRecognizer_Class/Reference/Reference.html%23//apple_ref/occ/cl/UIGestureRecognizer

Wow, A couple years late.. I ran into this problem, but solved it with a static variable:

- (IBAction)handleGesture:(UIPanGestureRecognizer *)recog {

    CGPoint loc = [recognizer locationInView:self.container];
    static CGFloat origin = 0.0f;

    switch (recognizer.state) {
        case UIGestureRecognizerStateBegan:
            origin = loc.x;
            break;
        case UIGestureRecognizerStateChanged:
        case UIGestureRecognizerStatePossible:
            // origin is still set here to the original touch point!
            break;
        case UIGestureRecognizerStateEnded:
            break;
        case UIGestureRecognizerStateFailed:
        case UIGestureRecognizerStateCancelled:
            break;
    }

}

The variable is only set when recognizer state is UIGestureRecognizerBegan. Since the variable is static, the value persists to later method calls.

For my case, I just needed the x coordinate, but you can change it to a CGPoint if you need.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM