I found when I have UIGesture, like UITapGesture, it always take precedence over the touchbegan methods etc.
Reading books, If setting "Delayed Begin", this should be true. But I don't set the "Delayed Begin"?
By default, touchesBegan: is delivered both to the gesture recognizer and to the view. It isn't touchesBegan: that is delayed by default - it's touchesEnded:, meaning that the gesture recognizer holds onto this until it can decide whether to recognize its gesture or not (and this is especially important for a multitap gesture, because the first touch may end but the gesture might still recognize if there's a further tap).
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.
Hi any one can explain me, what are the cases can i use the following UIGestureRecognizer Methods.
1. - (BOOL)canBePreventedByGestureRecognizer:(UIGestureRecognizer*)preventingGestureRecognizer
- (BOOL)canPreventGestureRecognizer:(UIGestureRecognizer *)preventedGestureRecognizer
2. - (void)requireGestureRecognizerToFail:(UIGestureRecognizer *)otherGestureRecognizer
3. - (BOOL)shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer*)otherGestureRecognizer
- (BOOL)shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer*)otherGestureRecognizer
Kindly give the use case of all this three methods, if u have any example kindly comment here.
Thanks in advance.
Please read the Apple's Documentation.
canBePreventedByGestureRecognizer:
Overridden to indicate that the specified gesture recognizer can prevent the receiver from recognizing a gesture.
canPreventGestureRecognizer:
Overridden to indicate that the receiver can prevent the specified gesture recognizer from recognizing its gesture.
requireGestureRecognizerToFail:
Creates a dependency relationship between the receiver and another gesture recognizer.
shouldBeRequiredToFailByGestureRecognizer:
Overridden to indicate that the receiver should be required to fail by the specified gesture recognizer.
shouldRequireFailureOfGestureRecognizer:
Overridden to indicate that the receiver requires the specified gesture recognizer to fail.
All those methods simply establish dependancies of different types between different recognizers. Certain gesture recognizers may use similar gestures, and these are typically set up so that one takes precedence over another.
For example, a scrolling gesture and a swipe gesture are similar in that they both involve a touch moving in a particular direction, so you might set up the swipe recognizer such that it requires the scrolling recognizer to fail before the swipe can be recognized. Or, you could set them up so that the scrolling recognizer prevents the swipe recognizer from being activated while the user is scrolling.
I have a UITableView which I present in a UIPopoverController. The table view presents a list of elements that can be dragged and dropped onto the main view.
When the user begins a pan gesture that is principally vertical at the outset, I want the UITableView to scroll as usual. When it's not principally vertical at the outset, I want the application to interpret this as a drag-and-drop action.
My unfortunately lengthy journey down this path has compelled me to create a custom UIGestureRecognizer. In an attempt to get the basics right, I left this custom gesturer as an empty implementation at first, one that merely calls the super version of each of the five custom methods Apple says should be overridden:
(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
(void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;
(void)reset;
This results in nothing happening, i.e. the custom gesture's action method is never called, and the table view scrolls as usual.
For my next experiment, I set the gesture's state to UIGestureRecognizerStateBegan in the touchesBegan method.
This caused the gesture's action method to fire, making the gesture appear to behave just like the standard UIPanGestureRecognizer. This obviously suggested I was responsible for managing the gesture's state.
Next up, I set the gesture's state to UIGestureRecognizerStateChanged in the touchesMoved method. Everything still fine.
Now, instead, I tried setting the gesture's state to UIGestureRecognizerStateFailed in the touchesMoved method. I was expecting this to terminate the gesture and restore the flow of events to the table view, but it didn't. All it did was stop firing the gesture's action method.
Lastly, I set the gesture's state to UIGestureRecognizerStateFailed in the touchesBegan method, immediately after I had set it to UIGestureRecognizerStateBegan.
This causes the gesture to fire its action method exactly once, then pass all subsequent events to the table view.
So...sorry for such a long question...but why, if I cause the gesture to fail in the touchesBegan method (after first setting the state to UIGestureRecognizerStateBegan), does it redirect events to the table view, as expected. But if I try the same technique in touchesMoved (the only place I can detect that a move is principally vertical), why doesn't this redirection occur?
Sorry for making this more complicated than it actually was. After much reading and testing, I've finally figured out how to do this.
First, creating the custom UIGestureRecognizer was one of the proper solutions to this issue, but when I made my first test of the empty custom recognizer, I made a rookie mistake: I forgot to call [super touches...:touches withEvent:event] for each of the methods I overrode. This caused nothing to happen, so I set the state of the recognizer to UIGestureRecognizerStateBegan in touchesBegan, which did result in the action method being called once, thus convincing me I had to explicitly manage states, which is only partially true.
In truth, if you create an empty custom recognizer and call the appropriate super method in each method your override, your program will behave as expected. In this case, the action method will get called throughout the dragging motion. If, in touchesMoved, you set the recognizer's state to UIGestureRecognizerStateFailed, the events will bubble up to the super view (in this case a UITableView), also as expected.
The mistake I made and I think others might make is thinking there is a direct correlation between setting the gesture's state and the chronology of the standard methods when you subclass a gesture recognizer (i.e. touchesBegan, touchesMoved, etc.). There isn't - at least, it's not an exact mapping. You're better off to let the base behavior work as is, and only intervene where necessary. So, in my case, once I determined the user's drag was principally vertical, which I could only do in touchesMoved, I set the gesture recognizer's state to UIGestureRecognizerStateFailed in that method. This took the recognizer out of the picture and automatically forwarded a full set of events to the encompassing view.
For the sake of brevity, I've left out a ton of other stuff I learned through this exercise, but would like to point out that, of six or seven books on the subject, Matt Neuburg's Programming IOS 4 provided the best explanation of this subject by far. I hope that referral is allowed on this site. I am in no way affiliated with the author or publisher - just grateful for an excellent explanation!
That probably happens because responders expect to see an entire touch from beginning to end, not just part of one. Often, -touchesBegan:... sets up some state that's then modified in -touchesMoved..., and it really wouldn't make sense for a view to get a -touchesMoved... without having previously received -touchesBegan.... There's even a note in the documentation that says, in part:
All views that process touches,
including your own, expect (or should
expect) to receive a full touch-event
stream. If you prevent a UIKit
responder object from receiving
touches for a certain phase of an
event, the resulting behavior may be
undefined and probably undesirable.
I'd like to implement multitouch, and I was hoping to get some sanity checks from the brilliant folks here. :)
From what I can tell, my strategy to detect and track multitouch is going to be to use the touchesBegan _Moved and _Ended methods and use the allTouches method of the event parameter to get visibility on all relevant touches at any particular time.
I was thinking I'd essentially use the previousLocationInView as a way of linking touches that come in with my new events with the currently active touches, i.e. if there is a touchBegan for one that is at x,y = 10,14, then I can use the previous location of a touch in the next message to know which one this new touch is tied to as a way of keeping track of one finger's continuous motion etc. Does this make sense? If it does make sense, is there a better way to do it? I cannot hold onto UITouch or UIEvent pointers as a way of identifying touches with previous touches, so I cannot go that route. All I can think to do is tie them together via their previouslocationInView value (and to know which are 'new' touches).
You might want to take a look at gesture recognizers. From Apple's docs,
You could implement the touch-event handling code to recognize and handle these gestures, but that code would be complex, possibly buggy, and take some time to write. Alternatively, you could simplify the interpretation and handling of common gestures by using one of the gesture recognizer classes introduced in iOS 3.2. To use a gesture recognizer, you instantiate it, attach it to the view receiving touches, configure it, and assign it an action selector and a target object. When the gesture recognizer recognizes its gesture, it sends an action message to the target, allowing the target to respond to the gesture.
See the article on Gesture Recognizers and specifically the section titled "Creating Custom Gesture Recognizers." You will need an Apple Developer Center account to access this.