I need to capture the following touch events on iOS and interpret them:
Finger A touch down within a UIButton (stays down)
Finger B performs a pan gesture elsewhere on the screen, providing continuous callbacks. (I plan to use a UIPanGestureRecognizer to implement this functionality).
Finger A touch up within the same UIButton
In essence, pressing and holding the UIButton puts the app into a special mode which lasts only as long as the button is held. Pan gestures performed during this time do different things to when the button is not pressed.
What I've tried so far:
hooking up the UIButton Touch Down and Touch Up Inside to IBActions in my UIViewController subclass. I've also
Problem encountered: the Touch Up Inside action is not called when another gesture happens on the screen from another finger while the button is pressed. This is because the original touch is cancelled.
attaching a UITapGestureRecognizer to the UIButton
Problem encountered: This only fires when the finger leaves the screen, hence it cannot be used to put the app into a special mode while the button is pressed.
I need to use a UIButton rather than a UIView so that the button has correct highlighting behaviour when pressed.
What is the best overall approach, given the problems I've encountered so far?
Use UILongPressGestureRecognizer with short minimumPressDuration on the "button"
http://developer.apple.com/library/ios/#documentation/uikit/reference/UILongPressGestureRecognizer_Class/Reference/Reference.html#//apple_ref/occ/cl/UILongPressGestureRecognizer
Important thing is to use gesture recognizer delegate to make sure that gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer: returns YES for these recognizers. Then, when your UIPanGestureRecognizer calls the event handler, you can check the state of your UILongPressGestureRecognizer and if the button isn't pressed just ignore the pan gesture.
Related
I'm trying to implement a drag and drop UI for my UIView using the pan gesture recognizer. I have that piece of code working, but now I want to only execute the drag and drop logic only AFTER the user has long pressed on my to-be-dragged view.
I'm implementing the code in the below question
Recognize long press and pan gesture recognizers together but it's not exactly what I want. Any idea?
Set up your view controller as the delegate of the pan gesture recognizer.
Implement the gestureRecognizerShouldBegin(_:) method. Return false until after the long press gesture recognizer fires.
Found another post whose title was a bit misleading so I didn't look into it too much the first time.
Combine longpress gesture and drag gesture together
It turns out, UILongPressGesture already can help me achieve the drag and drop effect that I want. That means I do NOT need the UIPanGesture at all. I just used selector/handler for the pan gesture for the long press gesture. Except the long press gesture doesn't have the translation properties, so I use
myView.center = sender.location(in: myView.superview)
to achieve the same dragging effect.
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 tried setting up a view with a longpress gesture and a stepper configured for continuous updates. With the longpress, the continuous feature of the stepper does not occur. For now, I've disabled the longpress. I guess I don't need it. But for future reference, how would I allow for both to coexist?
Just to be clear, here is the way the screen was set up when I tried this.
App was set up with a simple view controller.
A subview was added to this view (could have been a controller, but I just made it a UIView).
Several labels and stepper were added to this subview.
The steppers were wired up as outlets and actions.
A longpress recognizer was added to the main view in IB.
For completeness, a tap gesture was also added to the main view in IB.
Taps on the main view function as expected. Taps on the steppers function as expected. Longpress on the main view functions as expected. Longpress on the stepper does not.
I modified the code called by the longpress to check for the frame of the subview and not act if the touch location was within that rectangle, but that didn't make a difference. I did not try getting the longpress to fail in that situation, but I suppose I'll try that next. EDIT: OK, maybe not. There doesn't seem to be an API for that. However, there is this kludge, that I'm not going to try.
Attached is a screen shot from profiler with an inverted call tree so you can see what each item is being called by.
darkStepped: is the IBAction that is called by the stepper. If the stepper were triggered by a gesture recognizer, wouldn't I expect to see the gesture recognizer in the call tree?
If the stepper were triggered by a gesture recognizer, wouldn't I expect to see the gesture recognizer in the call tree?
The stack trace reveals that the stepper's _updateCount method is dispatched through a timer.
This could be related to the fact that a stepper has an "autoIncrement" mode where, as long as your keep it pressed, it will update at a given (varying) rate. So, instead of simply calling _updateCount, the stepper sets up a timer to handle this behaviour.
For whatever reason the timer is used, the timer explains why you do not see the gesture recogniser in the stack trace.
In your case what happens is that the stepper gets the touches, handles them, and do not forward them to any gesture recognisers attached to it.
This can be explained as follows, although this snippet does not explicitly mention a long press recogniser in relation to a UIStepper control:
According to Apple Docs:
Interacting with Other User Interface Controls
In iOS 6.0 and later, default control actions prevent overlapping gesture recognizer behavior. For example, the default action for a button is a single tap. If you have a single tap gesture recognizer attached to a button’s parent view, and the user taps the button, then the button’s action method receives the touch event instead of the gesture recognizer. This applies only to gesture recognition that overlaps the default action for a control, which includes:
A single finger single tap on a UIButton, UISwitch, UIStepper, UISegmentedControl, and UIPageControl.
...
If you have a custom subclass of one of these controls and you want to change the default action, attach a gesture recognizer directly to the control instead of to the parent view. Then, the gesture recognizer receives the touch event first. As always, be sure to read the iOS Human Interface Guidelines to ensure that your app offers an intuitive user experience, especially when overriding the default behavior of a standard control.
So, it seems you can attach the gesture recogniser directly to the control (possibly you need to subclass UIStepper for this to work, I am not really sure how to interpret the last paragraph). Hopefully this will not disable the basic workings of the stepper (but maybe it will).
After carefully reviewing Apple's docs again, I've found the solution. I added the view controller as the delegate to the longpress gesture recognizer
self.longPress.delegate = self;
(and, of course, adding <UIGestureRecognizerDelegate> to the interface, and then added this method to the view controller:
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
// Determine if the touch is inside the custom subview
if (gestureRecognizer == self.longPress) {
CGPoint touchLocation = [touch locationInView:self.view];
if (CGRectContainsPoint(self.antControl.frame, touchLocation)) {
return NO;
}
}
return YES;
}
This way the gesture recognizer doesn't even get called when the longpress occurs within the frame of self.antControl, which is the subview mentioned in the question.
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.
I have a UIButton (a subclass of one, actually) that interacts with the user via the touchesbegan: and touchesmoved: functions.
What I would like is for the user to be able to press down the button, drag their finger away, and have a second finger touch the button (all while the first finger has never left the screen).
Problem is, the second touch event never calls touchesbegan: unless the first finger has been released.
Is there some way to override this, or am I trying to do the impossible?
Have you tried setting multipleTouchesEnabled to YES?
If the interactions are using touchesbegan: and touchesmoved: then use a UIView instead of a UIButton. A button is a UIControl, and the way to interact with UIControls is
- (void)addTarget:(id)target action:(SEL)action forControlEvents:(UIControlEvents)controlEvents.
I'm not sure this two ways of getting events mix well.