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 (i.e. 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:
- UILongPressGestureRecognizer, and set delay to 0
- UIPanGestureRecognizer, and capture start point in shouldReceiveTouch
If you have other gestures (e.g. 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 (e.g. 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:
First, you'll need to register as the delegate and listen for the shouldReceiveTouch event.
Once that happens, save the touch point in some variable (not the gesture point!).
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!)
}