I am using a UIPanGestureRecogniser to implement drag and drop. When the drag starts I need to identify the object that is being dragged. However the objects are relatively small. And if the user doesn't hit the object right in the centre of the object it isn't getting dragged.
The problem is that when the gesture handler is first called with the state UIGestureRecognizerStateBegan, the finger has already moved several pixels, and so [UIPanGestureRecognizer locationInView:] returns that point, which is not where the gesture truly started. That makes sense as it can only recognize a pan after a few pixels of movement. However, I need the absolute start of the gesture, not the position after the gesture has first been recognized.
I'm thinking that maybe I need to implement a tap gesture recognizer as well, purely to capture the first touch. But that seems like a hack for what is not an unusual requirement. Is there no other way of getting that first touch from within the pan gesture recognizer?
UIGestureRecognizerDelegate protocol provides methods gestureRecognizerShouldBegin: and gestureRecognizer:shouldReceiveTouch: that can help you evaluate the touches before the pan has transitioned to state UIPanGestureRecognizerStateBegan
Related
I find this doc a bit confusing:
https://developer.apple.com/documentation/uikit/uipangesturerecognizer
Specifically, the top of the doc says it's discrete:
A discrete gesture recognizer that interprets panning gestures.
Then the following description says:
A panning gesture is continuous. It begins (UIGestureRecognizer.State.began) when the user moves the minimum number of fingers allowed (minimumNumberOfTouches) enough distance for recognition as a pan. It changes (UIGestureRecognizer.State.changed) when the user moves a finger while pressing with the minimum number of fingers. It ends (UIGestureRecognizer.State.ended) when the user lifts all fingers.
So which is it? discrete or continuous?
My understanding is that discrete recognizer only calls callback action only when it's recognized (e.g. Swipe), but continuous recnogizers calls the callback action when it's moved as well. So pan gesture should be continuous. Am i right?
From the first link DonMag posts in his answer, About the Gesture Recognizer State Machine,
It explains that discrete gesture recognizers fire/fail once, then reset. Continuous gesture recognizers can go into a loop, returning a state of UIGestureRecognizer.State.changed as the user moves their finger.
Edit:
I think #OMGPOP figured out what's going on. It looks like The word "discrete" in the sentence "A discrete gesture recognizer that interprets panning gestures" should be "concrete". That must be a typo.
Supporting that idea is the fact that the description of the base UIGestureRecognizer class says "UIGestureRecognizer: The base class for concrete gesture recognizers." The UIGestureRecognizer is the abstract parent class of concrete classes like UIPanGestureRecognizer.
It seems to me this is mainly a "terminology" thing.
A UIPanGestureRecognizer is discreet in that it doesn't begin on touch... it enters a state of "possible." It only generates a .began event after the touch has moved enough distance for it to be recognized as a Pan. After that, it is continuous as it sends .changed events as the touch is moved.
You may find it helpful to review these Apple's docs (among others):
About the Gesture Recognizer State Machine
Implementing a Custom Gesture Recognizer
Implementing a Discrete Gesture Recognizer
Implementing a Continuous Gesture Recognizer
Although... you probably only need to know the "down-and-dirty" if you are, in fact, implementing your own gesture recognizer.
I have found no way in the documentation on how to specify the number of touches for UIPinchGestureRecognizer or UIRotationGestureRecognizer.
All I found anywhere is that it only works with two fingers, but by my experiments, it also works with 3 or even more fingers.
Furthermore in the action the property numberOfTouches also never returns the actual number of fingers.
I want to limit it only for two fingers because it gets all confused with other 3-finger recognizers.
Could you, please, suggest me a good way to do that? Thanks.
According to the docs UIPinchGestureRecognizer handles
[...] pinching gestures involving two touches [...]
Apparently it only considers two touches but allows additional touches to happen concurrently.
To answer your question: you can try to get the actual number of touches by other means and prevent the pinch action when that count is larger than 2. One way is to add more gesture recognizers which handle gestures on the same view (e.g. multiple UITapGestureRecognizers, one for each possible number of touches); another one is to override touchesBegan and touchesMoved of the view your gesture recognizer is installed on and use the count of the provided touches array.
(I'd go with the second approach first because managing multiple gesture recognizers in parallel can get problematic.)
Add a delegate to the pinch gesture recogniser you're concerned about.
Implement gestureRecognizer(_:, shouldRecognizeSimultaneouslyWith:) and return false if you want the pinch gesture to be ignored if there is another recogniser also in progress.
I'm making a game on the iPad where the player swipes up, down, left, or right to move the character. An attack is controlled by touchesBegan:withEvent:
My problem is that the character attacks whenever he moves.
Is there a way to set up a swipe gesture so the code doesn't run touchesBegan:withEvent: until it sees if the motion is the beginning of a swipe or not?
This is not too easy of a task. Without using some custom gestures I would suggest you to try the combination of UISwipeGestureRecognizer and UILongPressGestureRecognizer. I know this sound silly but it is not: An UILongPressGestureRecognizer acts pretty much the same as the pan gesture so even if the finger is dragged you will receive events. You need to set some proper minimum duration till it fires (depends on the swipe gesture) and some large minimum drag length so it doesn't get canceled for dragging. You need to remove the touch event methods then and move the code to long press gesture action.
To explain the result, your long press gesture will (if set correctly) work just the same as touch events except it will wait for specified duration. If in that duration a swipe is detected your long press gesture will not fire. Seems just what you need...
I'm new to developing iOS apps,
I've successfully implemented a Swipe Gesture Recognizer,
What I was wondering is if there is an easy to use recognizer like the swipe gesture. That would let you implement the homescreen page turning effect but just on a small view in the view controller?
If your unclear on what effect I mean, when you look at the iPhone's homescreen you can drag your finger and it responds instantly (unlike swipe) and also has some spring feeling to it, is this some effect I can use, or do I manually have to program this into the code if so is there a tutorial that explains this?
Thanks,
I hope my question makes sense.
Have a look at UIPanGestureRecognizer:
https://developer.apple.com/library/ios/documentation/uikit/reference/UIPanGestureRecognizer_Class/Reference/Reference.html
UIPanGestureRecognizer is a concrete subclass of UIGestureRecognizer
that looks for panning (dragging) gestures. The user must be pressing
one or more fingers on a view while they pan it. Clients implementing
the action method for this gesture recognizer can ask it for the
current translation and velocity of the gesture.
A panning gesture is continuous. It begins
(UIGestureRecognizerStateBegan) when the minimum number of fingers
allowed (minimumNumberOfTouches) has moved enough to be considered a
pan. It changes (UIGestureRecognizerStateChanged) when a finger moves
while at least the minimum number of fingers are pressed down. It ends
(UIGestureRecognizerStateEnded) when all fingers are lifted.
Clients of this class can, in their action methods, query the
UIPanGestureRecognizer object for the current translation of the
gesture (translationInView:) and the velocity of the translation
(velocityInView:). They can specify the view whose coordinate system
should be used for the translation and velocity values. Clients may
also reset the translation to a desired value.
Edit: The spring feeling part you would need to implement yourself. Since iOS 7 there is UIDynamics which contains different animators, for what you describe you may need UIGravityBehavior and maybe UICollisionBehaviour. Look at the WWDC 2013 videos for this topic, I think you will find some examples there.
I working with UIGestureRecognizer atm creating some map alike program.
My work require me to listen to both long press gesture and pan gesture separate, so each of them can do their own task.
But in one case, i need to listen to long press first to know which object is chosen. After that, when i begin to move my finger (without lift it up), that object will be move too. It kind of like drag a marker around in google map. But because my long press recognizer already fired, pan gesture recognizer didn't get fire until i tab on the screen again.
I tried something like
recognizer.enabled = NO;
recignizer.enabled = YES;
But it didn't help at all.
So i wonder if there anyway to cancel long press after it recognized (UIGestureRecognizerStateBegan) so pan gesture recognizer will be fire immediately when i begin to move. Or i have to use UIResponder to make it work ?
Thank for your advice.
You can actually use UILongPressGestureRecognizer to detect the pan gesture after the long tap. The only caveat is that, since UILongPressGestureRecognizer is a subclass of UIGestureRecognizer it does not have the -translationInView: that is so handy in UIPanGestureRecognizer.
You can anyway do these calculations on your own by keeping track of where the touch has moved after the long press, as it will keep firing the action bound to the gesture recognizer.
Take a look at this question, it may help to calculate the translation with the long press gesture.