I googled several other questions and tutorials but couldn't find an answer for my question. I want to detect if the user has touched/tapped/hold/clicked the screen. I tried with touchesBegan: withEvent: but it is not firing any events.
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
if ([touch view] == mapView_) {
NSLog(#"Touches began");
} else NSLog(#"Touches began");
}
Is there another way to detect user interaction throught touching the screen?
You have to use UITapGestureRecognizer.
Conform your class to UIGestureRecognizerDelegate protocol.
Instantiate the gesture recognizer. For example, to instantiate a UITapGestureRecognizer, we will do:
UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTapFrom:)];
Here, action is the selector which will handle the gesture. Here, our selector handleTapFrom will look something like:
- (void) handleTapFrom: (UITapGestureRecognizer *)recognizer
{
//Code to handle the gesture
}
The argument to the selector is the gesture recognizer. We can use this gesture recognizer to access its properties, for example, we can find the state of the gesture recognizer, like, UIGestureRecognizerStateBegan, UIGestureRecognizerStateEnded, etc.
Set the desired properties on the instantiated gesture recognizer. For example, for a UITapGestureRecognizer, we can set the properties numberOfTapsRequired, and numberOfTouchesRequired.
Add the gesture recognizer to the view you want to detect gestures for. In our sample code (I will be sharing that code for your reference), we will add gesture recognizers to an imageView with the following line of code:
[self.imageView addGestureRecognizer:tapGestureRecognizer];
After adding the gesture recognizer to the view, set the delegate for the gesture recognizer, i.e. the class which will handle all the gesture recognizer stuff. In our sample code, it would be like:
tapGestureRecognizer.delegate = self;
Note: Assign the delegate after adding the gesture recognizer to the view. Otherwise, the action method won’t be called.
Reference - Here
The solution was using UIPanGestureRecogniser.
Here's the code that solved the problems and stopped my headache:
UIPanGestureRecognizer *tap = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handleTapFrom:)];
[mapView_ setMultipleTouchEnabled:YES];
[mapView_ setUserInteractionEnabled:YES];
mapView_.gestureRecognizers = #[tap];
And then the selector method:
- (void) handleTapFrom: (UIPanGestureRecognizer*)recogniser {
NSLog(#"Pin");
}
You can subclass UIApplication if you are only interested in whether the user touched the screen or not, and implement this method:
- (void)sendEvent:(UIEvent *)event {
[super sendEvent:event];
if (event.type == UIEventTypeTouches) {
// handling code
}
}
In this case the main.m file would look like this:
int main(int argc, char * argv[])
{
#autoreleasepool {
return UIApplicationMain(argc, argv, #"UIApplicationSubclass", #"AppDelegate");
}
}
Related
I am trying to create a custom UIControl similar to a slider.
This control is to be the subview of a view that also has a tap gesture recognizer attached to it.
The problem now is that this tap gesture recognizer cancels the touches sent to my control. Is there a way I can override this from within the code of my control?
If I look into the standard controls in iOS it looks as if UIButton has a way of overriding the tap gesture recognizer but UISlider doesn't. So if I replace my custom control with a UIButton the tap gesture recognizer does not trigger its action, but if I replace it with a slider it does.
edit: I made a small project in Xcode to play around in. Download here https://dl.dropboxusercontent.com/u/165243/TouchConcept.zip and try to change it so that
The UICustomControl does not know about the tap gesture recognizer
The UICustomControl is not cancelled when the user taps down on the yellow box
The UICustomControl does not inherit from UIButton (that is a solution that does not feel right and might give me more headaches later on)
The code:
// inherit from UIButton will give the wanted behavior, inherit from UIView (or UIControl) gives
// touchesCancelled by the gesture recognizer
#interface UICustomControl : UIView
#end
#implementation UICustomControl
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{ NSLog(#"touchesBegan"); }
-(void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{ NSLog(#"touchesMoved"); }
-(void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{ NSLog(#"touchesEnded"); }
-(void) touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{ NSLog(#"touchesCancelled"); }
#end
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad
{
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(logTap:)];
[self.view addGestureRecognizer:tapRecognizer];
UIView *interceptingView = [[UICustomControl alloc]initWithFrame:CGRectMake(10, 10, 100, 100)];
interceptingView.userInteractionEnabled = YES;
interceptingView.backgroundColor = [UIColor yellowColor];
[self.view addSubview: interceptingView];
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void) logTap: (id) sender
{
NSLog(#"gesture recognizer fired");
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
You can configure the gesture recognizer to not cancel touches in the view it's attached using the "cancels touches in view" property:
myGestureRecognizer.cancelsTouchesInView = NO;
I'm a little bit late, but for those (like me) stumbling into this question, I used an alternative solution:
Use the delegate of the gesture recogniser.
UITapGestureRecognizer *tapGestRec = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(dismissInfoBox:)];
tapGestRec.delegate = self;
[self.view addGestureRecognizer:tapGestRec];
Then do a sort of hit test in the shouldReceiveTouch delegate function when the gesture recogniser wants to handle/swallow a touch.
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
CGPoint location = [touch locationInView:self.view];
return !CGRectContainsPoint(self.myCustomControl.frame, location) && !CGRectContainsPoint(self.myOtherCustomControl.frame, location);
}
I did all this in my ViewController, this way the UIControl does not have to know about any sibling views and the gesture recogniser does not 'steal' taps from my custom controls and only handles 'uncaught' taps.
Also, this way you won't trigger both the gesture recogniser and the custom control, which would happen with cancelsTouchesInView.
BTW, maybe it works with UIButton because UIButton uses gesture recognisers internally? I think they understand each other, while UIControls and recognisers do not. Not sure though.
override gestureRecognizerShouldBegin(_:) in your UIControl subclass.
public override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureRecognizer.isKind(of: UITapGestureRecognizer.self) {
return false
} else {
return super.gestureRecognizerShouldBegin(gestureRecognizer)
}
}
You should follow this tutorial
http://www.raywenderlich.com/1768/uiview-tutorial-for-ios-how-to-make-a-custom-uiview-in-ios-5-a-5-star-rating-view
It shows you How to make a custom uiview.
And then do follow this one
http://www.raywenderlich.com/29474/ipad-for-iphone-developers-101-in-ios-6-custom-input-view-tutorial
This question already has answers here:
UIScrollView touchesBegan
(8 answers)
Closed 9 years ago.
I have subclassed UIScrollView and implemented
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
method in it.
But Nothing happens when click or hold the scroll view!
Edit: I also need to use touchesEnded method
I think UIScrollView has two gesture recognizer. They are responsible for handling touch sequences, so they swallow the touch events.
Use scrollView delegate methods to handle drag gestures or the
touchesShouldBegin:withEvent:inContentView:
method to handle scrollview content touches and
touchesShouldCancelInContentView:
to cancel it.
As alternative you can manipulate the panGesture recognizer of the scrollView to pass the event to the next responser.
In your subclass of UIScrollView override the hitTest method like this:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
UIView *result = nil;
for (UIView *child in self.subviews)
if ([child pointInside:point withEvent:event])
if ((result = [child hitTest:point withEvent:event]) != nil)
break;
return result;
}
I think you are using scrollview as a subview.So in that case you can use gesture coz same problem i've face it.
You can do using UITapGestureRecognizer like this ...
-(void)viewDidLoad
{
UITapGestureRecognizer *gr = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleGesture:)];
gr.delegate = self;
[self.scrollView addGestureRecognizer:gr];
}
- (void)handleGesture:(UIGestureRecognizer *)gestureRecognizer
{
// do here whatever you wanna ..........
}
Solved!
Used LongPressedGestureRecognizer. The action method keeps getting called till the user holds the view. From that i can figure out the state of the gestureRecognizer (UIGestureRecognizerStateBegan, ...Ended, ...Cancelled)
I got an error. "No visible interface for UISwipeGestureRecognizer declares the selector 'touchesMoved:withEvent:'"
I looked at documentation and found touchesMoved:withEvent at UIGestureRecognizer class. How do I solve this error?
#interface MySwipeRecognizer : UISwipeGestureRecognizer
#implementation MySwipeRecognizer
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesMoved:touches withEvent:event];
}
#end
Unless I'm misunderstanding the question, a UISwipeGestureRecognizer does all the touch handling for you. Your code would look something like this:
UISwipeGestureRecognizer *swipe = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(onSwipe:)];
// set a direction for the swipe
[swipe setDirection:UISwipeGestureRecognizerDirectionLeft];
// self is a view to add the recognizer to:
[self addGestureRecognizer:swipe];
.
.
.
- (void) onSwipe:(id)sender
{
// a swipe has been recognized!
}
UIGestureRecognizer is an ABSTRACT class, so concrete implementations like UISwipeGestureRecognizer do all the touch event handling for you. If you're trying to create your own custom gesture recognizer, you'd subclass UIGestureRecognizer.
I've created a mini pop-up menu for the iPhone in a UIView, and I'd like the user to be able to dismiss the view if they do anything other than select one of the options. So, if a user taps/swipes/pinches any other element on the screen, the pop-up view should disappear.
However, I don't want to detect a gesture that will stop something else from happening... For example, there is a UITableView underneath and if I swipe up or down on it, I want it to move as expected as well as dismissing the mini pop-up view.
Should I use multiple gesture recognizers, or should I use touchesBegan, or is there a better way of doing it?
Put this in your UIViewController
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
if (touch.view!=yourView && yourView) {
[yourView removeFromSuperview];
yourView=nil;
}
}
EDIT: changes made to detect touch and remove only if view exists
EDIT2: Well you could add the following to your UIButtons/UITableView methods
if (yourView) {
[yourView removeFromSuperview];
yourView=nil;
}
or add touchesBegan:withEvent: as a touchDown event to your buttons.
Both annoying to do but can't see another way to do it as the touchesBegan method doesn't get called with interactive elements.
EDIT3: Right scrap that, think i've nailed it
in your interface add the UIGestureRecognizerDelegate
#interface ViewController : UIViewController <UIGestureRecognizerDelegate> {
then in your viewDidLoad add this
UITapGestureRecognizer *tapped = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapMethod)];
tapped.delegate=self;
tapped.numberOfTapsRequired = 1;
[self.view addGestureRecognizer:tapped];
then in your viewController add these 2 methods
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
if (touch.view!=yourView && yourView) {
return YES;
}
return NO;
}
-(void)tapMethod {
[yourView removeFromSuperview];
yourView=nil;
}
Update: The problem seems to be the dependency on another GestureRecognizer to fail. See comments and test project below this question!
In my iPhone app I have a view with multiple UIButtons as subviews. The view also has a UITapGestureRecognizer which is listening for taps with two fingers.
When a two-finger-tap occurs on the view I don't want the buttons to react to the tap, even if one of the fingers was inside the button. I thought this is what "cancelsTouchesInView" is for, but that doesn't work.
My question now is: How to tell my buttons to ignore taps when a gesture is recognized?
Edit: This is my gesture recognizer.
UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(doubleTapped:)];
[doubleTap setNumberOfTouchesRequired:2];
[doubleTap setNumberOfTapsRequired:1];
[doubleTap setCancelsTouchesInView:YES];
[doubleTap setDelaysTouchesBegan:YES];
[doubleTap setDelaysTouchesEnded:YES];
[self.view addGestureRecognizer:doubleTap];
[doubleTap release];
According to an Apple dev this is a bug. I filed a bug report with Apple.
Thanks a lot for your hints, Deepak and gcamp!
Bug report:
Summary:
When adding two UITapGestureRecognizers to a view where one requires the other to fail (requiresGestureRecognizerToFail:) the cancelsTouchesInView property of the first gesture recognizer is ignored.
Steps to Reproduce:
1. Create two UITapGestureRecognizers (r1 and r2)
2. Configure r1 to require two touches and one tap and to delay touchesBegan
3. Configure r2 to require two touches and two taps and to delay touchesBegan
4. Configure r1 to require r2 to fail [r1 requiresGestureRecognizerToFail:r2]
5. Add r1 and r2 to a view
6. Place a UIButton in the view
7. Tap with two fingers on the view, one should hit the button.
Expected Results:
r1 should be recognized and the button tap should be canceled (cancelsTouchesInView defaults to YES for UITapGestureRecognizers).
Actual Results:
r1 is recognized but the button touchedUpInside event is fired, too.
Regression:
cancelTouchesInView works fine for r1 once you remove the dependency on r2 (step 4).
There's a method on UIGestureRecognizer that respond to your question
- (void)requireGestureRecognizerToFail:(UIGestureRecognizer *)otherGestureRecognizer
Basically you're requiring that the two tap recognizer fail before accepting the single tap one.
So,
[singleTapRecognizer requireGestureRecognizerToFail:twoTapRecognizer];
Use the delaysTouchesBegan property. Set it to YES.
Alternative
Disable user interaction on the buttons and attach a single finger tap recognizer like mentioned. In the tap handler, check if the tap falls within the bounds of the button. If it is within the bounds of a button, do [theButton sendActionsForControlEvents:UIControlEventTouchUpInside];. This will trigger the touch up event as desired even though the user interaction is disabled.
Mark, I ran into the same bug. If you'll post your radar #, I'll dupe it in bugreporter.
I wrote the following workaround. I subclass UITapGestureRecognizer, then use the subclass to track the touches which triggered the gesture action. If we get the same touches in touchesEnded on the view, we redirect to touchesCancelled. 'immediateTarget' is needed because the touchesEnded call on the view comes after the gesture's touchesEnded but before the gesture's action is called.
RPTapGestureRecognizer.h:
#import <UIKit/UIKit.h>
#import <UIKit/UIGestureRecognizerSubclass.h>
#interface RPTapGestureRecognizer : UITapGestureRecognizer
#property (nonatomic, retain) NSSet *lastTouches;
- (void)setImmediateTarget:(id)inTarget action:(SEL)inAction;
#end
RPTapGestureRecognizer.m:
#import "RPTapGestureRecognizer.h"
#interface RPTapGestureRecognizer ()
#property (nonatomic) SEL immediateAction;
#property (nonatomic, assign) id immediateTarget;
#end
#implementation RPTapGestureRecognizer
#synthesize lastTouches;
#synthesize immediateAction, immediateTarget;
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
self.lastTouches = nil;
[super touchesBegan:touches withEvent:event];
}
- (void)setImmediateTarget:(id)inTarget action:(SEL)inAction
{
self.immediateTarget = inTarget;
self.immediateAction = inAction;
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
UIGestureRecognizerState startingState, finalState;
startingState = self.state;
[super touchesEnded:touches withEvent:event];
finalState = self.state;
if (startingState != finalState &&
finalState == UIGestureRecognizerStateEnded) {
/* Must copy; the underlying NSCFSet will be modified by the superclass, removing the touches */
self.lastTouches = [[touches copy] autorelease];
if (self.immediateAction)
[self.immediateTarget performSelector:self.immediateAction
withObject:self];
}
}
- (void)dealloc
{
self.lastTouches = nil;
self.immediateTarget = nil;
[super dealloc];
}
#end
In the view:
#synthesize lastTapGesture
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
if (self.lastTapGesture && [self.lastTapGesture.lastTouches isEqualToSet:touches]) {
[self touchesCancelled:touches withEvent:event];
self.lastTapGesture = nil;
return;
}
/* touches ended implementation here */
}
- (void)willHandleMouseTapGesture:(RPTapGestureRecognizer *)tapGesture
{
/* Because one tap gesture is dependent upon another, the touchesEnded method is still going to be called, instead of touchesCanceled.
* This is an Apple bug against all versions of iOS at least up to 5.0. It will be called in the run loop before tapGesture's action is called.
*
* See http://stackoverflow.com/questions/6188997/how-to-cancel-button-tap-if-uigesturerecognizer-fires/6211922#6211922 for details.
*/
self.lastTapGesture = tapGesture;
}
- (void)configureGestures
{
/* Two finger taps */
RPTapGestureRecognizer *tapGestureOne;
tapGestureOne = [[[RPTapGestureRecognizer alloc] initWithTarget:self
action:#selector(handleMouseTapGesture:)] autorelease];
tapGestureOne.numberOfTapsRequired = 1;
tapGestureOne.numberOfTouchesRequired = 2;
[tapGestureOne setImmediateTarget:self action:#selector(willHandleMouseTapGesture:)];
[self addGestureRecognizer:tapGestureOne];
[myGestures addObject:tapGestureOne];
RPTapGestureRecognizer *tapGestureTwo;
tapGestureTwo = [[[RPTapGestureRecognizer alloc] initWithTarget:self
action:#selector(handleMouseTapGesture:)] autorelease];
tapGestureTwo.numberOfTapsRequired = 2;
tapGestureTwo.numberOfTouchesRequired = 2;
[tapGestureTwo setImmediateTarget:self action:#selector(willHandleMouseTapGesture:)];
[self addGestureRecognizer:tapGestureTwo];
}