addTarget:action:forControlEvents: ignored when interacting with separate UITapGestureRecognizer - ios

I'm somewhat new to iOS programming
I have some code (abridged) that looks like the following
UIView *someSubView = [[UIView alloc] initWithFrame:...];
[self addSubView:someSubView];
[someSubView addTarget:self action:#selector(_handleTapOnView:) forControlEvents:UIControlEventTouchUpInside];
_tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(_handleTap:)];
_tapGestureRecognizer.delegate = self;
[self.view addGestureRecognizer:_tapGestureRecognizer];
Unfortunately the gesture recognizer triggers and my views addTarget call does not. I've tried commenting out the gesture recognizer code and it works, so I know its not the call to addTarget on the subview.
I solved this initially by using the gestureRecognizer:shouldReceiveTouch: and doing a hit test for the sub view, but I feel like I'm missing some fundamental understanding here that wouldn't require me adding a manual hit test.
Its important to note that I don't want the code in the _handleTap in the _tapGestureRecognizer to execute when I have tapped on my subview.
Any guidance here? Thanks!

Try using:
_tapGestureRecognizer.cancelsTouchesInView = NO;
otherwise the gesture recogniser will intercept the touches and will not forward them further (in other words, the gesture recogniser gets the touch, handles it, and since it cancel it, no other object gets the touch). By not cancelling, the touch is forwarded for any other object (recognisers or views) to handle it.

Related

Put gesture recognizers in view or controller class

I was looking for GestureRecognizer tutorials on the web and saw that most people used for example UIPanGestureRecognizer directly in the view. Is this common practice, if so, why not in the controller?
I generate my views in the controller and have methods that I would need to call, when the gesture is used. How should the delegation method look like, if my gesture recognizer is in the view class, and the method to be called in the controller class?
A simple UIGestureRecognizer implementation, and the most common one in my opinion is as follows:
//this is where you create the view that you want to add the gesture recognizer
UIImageView * imageView = [[UIImageView alloc] initWithFrame:frame];
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] init];
tapRecognizer.numberOfTapsRequired = 1;
[tapRecognizer addTarget:self action:#selector(imageViewTapped:)];
imageView.userInteractionEnabled = YES;
[imageView addGestureRecognizer:tapRecognizer];
After creating the gesture recognizer and adding it to a UIView subclass you can get the taps with the function you pointed above:
- (void)imageViewTapped:(UIGestureRecognizer *)gestureRecognizer
{
if ( gestureRecognizer.state == UIGestureRecognizerStateRecognized )
{
NSLog(#"tap recognized!");
}
}
So basically, you dont need to worry about the controller/view dilemma.
You always add the gesturerecognizer to a view. and when adding the gesture recognizer you add a target function, which you need to implement in the controller class.
P.S This SO Question might clarify in detail of the dilemma you are facing
In one sense, yes, this violate the MVC pattern. As you say, the view shouldn't have anything to do with how to control it, it's a better habit to group such code in another part of the application.
You can drag an gesture into the storyboard(into the view controller),too.Then from the document outline you make an action(in the vc) for that gesture

UILongPressGestureRecognizer on UIButton not working

I have a longpress gesture recognizer that I create in ViewDidLoad then attach to a button like this, the button is created in the storyboard and linked to my class.
UILongPressGestureRecognizer *hold = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(secretChange:)];
hold.minimumPressDuration = 5.0;
hold.delegate = self;
[_button addGestureRecognizer:hold];
The class conforms to the GestureRecognizer protocol and I have my selector here
- (void)secretChange:(UILongPressGestureRecognizer *)sender {
// Some stuff
NSLog(#"Secret");
}
The selector is not being called and I cannot figure out why, this seems to be the code everyone gives out on the internet, I have tried removing the minimum duration to make sure I didn't accidentally set it ridiculously long
UPDATE: I am actually adding this gesture recognizer to multiple buttons like this
[_button1 addGestureRecognizer:hold];
[_button2 addGestureRecognizer:hold];
[_button3 addGestureRecognizer:hold];
What is happening is the gesture recognizer is only being applied to the last button I add it to. How do I get the gesture recognizer added to ALL the buttons? Do I need to make a new one for every button?
You should have three instance of UILongPressGestureRecognizer.
Before add a gesture recognizer to a new view, the addGestureRecognizer method will remove the gesture recognizer from the view it has been attached to.

iOS gestureRecognizer not handled subviews

i have the following problem: in my iphone app i have a UIView that holds 0 to several subviews. Those subviews may overflow the parent and therefore be hidden and all of them are UIButtons. I added a UISwipeGestureRecognizer to the UIView to move the buttons around which works great. However it only works when the gesture is done on background, the UIButtons interfere with the gesture recognizer.
How i can i pass the gesture through? Btw. i still need the tap of the Buttons.
Thanks!
EDIT:
I tried to add the gesture recognizer to the UIButtons too, but it is not triggered... Although performing the swipe gesture prevents the UIButton from going to highlighted state it doesn't trigger the gesture. I added setDelaysTouchesBegan:YES as suggested in UIButton and Swipe Gesture. That's how i do it right now:
UIButton *breadcrumb = [UIButton buttonWithType:UIButtonTypeCustom];
[breadcrumb setImage:[UIImage imageNamed:#"Next"] forState:UIControlStateNormal];
[breadcrumb setTitle:title forState:UIControlStateNormal];
[breadcrumb.titleLabel setFont:[UIFont systemFontOfSize:12.0f]];
[breadcrumb sizeToFit];
[breadcrumb setTag:level];
UISwipeGestureRecognizer *gr = [[UISwipeGestureRecognizer alloc] init];
[gr setDelegate:self];
[gr setDirection:UISwipeGestureRecognizerDirectionRight];
[gr setDelaysTouchesBegan:YES];
[self.tableView addGestureRecognizer:gr];
[breadcrumb addGestureRecognizer:gr];
EDIT 2:
I have now subclassed UIButton and initilizing it now like so: [BreadcrumbButton buttonWithType:UIButtonTypeCustom]. In the initializer i added the button itsself as a listener to all touch events [self addTarget:self action:#selector(eventReceiver:) forControlEvents:UIControlEventAllTouchEvents]; to inspect whats going on.
- (void)eventReceiver:(UIButton *)btn {
NSLog(#"Reveived event: %# ---------------", btn);
for(UIGestureRecognizer *gr in ev.gestureRecognizers) {
NSLog(#"Gesture: %#", gr);
}
}
What i see is that a) the button has just one gesture recognizer added and b) that this UISwipeGestureRecognizer jumps to state Possible during swipe but does not forward to its delegate methods.
You would have to subclass the UIButton and over ride the tap delegate callback and forward this call to whatever is handling a UISwipeGestureRecognizer. Unless you add the gesture recognizer on the UIButton, it will always call it's touch handler before the view behind it. You can also explicitly tell the button to not handle it's touch events thus passing the touch event down the chain (via userInteractionEnabled), but as you've already stated you do not want this. The best way to go about this would be by creating a subclass of UIButton and handling the touch events there and/or forwarding the events using delegation. Pressing the button is a touch event, so you may just want to add a tap gesture recognizer to the button and call the IBAction from that and then have the swipegesturerecognizer forward a delegate call.

Why does this UITapGestureRecognizer not recognize taps?

I'm having a bit of a problem with a UITapGestureRecognizer. I create it this way:
self.userInteractionEnabled = YES;
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTap:)];
tapRecognizer.cancelsTouchesInView = NO;
tapRecognizer.delaysTouchesBegan = YES;
tapRecognizer.delegate = self;
tapRecognizer.numberOfTapsRequired = 1;
tapRecognizer.numberOfTouchesRequired = 1;
[self addGestureRecognizer:tapRecognizer];
In the header file I also include and implement the shouldReceiveTouch: method like so:
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
return YES;
}
I've looked through countless threads of people with similar problems and can't seem to find a solution. I've heard that if the view you're adding the gesture recognizer has subviews with userInteractionEnabled set to YES, that could possibly interfere with the tap recognition, so I also include this:
for(UIView *subview in self.subviews) {
subview.userInteractionEnabled = NO;
}
Anyone know why the gesture recognizer doesn't work?
Edit:
Here are some details:
I'm adding the UITapGestureRecognizer to a UIView subclass.
I add the gesture recognizer in the subclass' initWithFrame: method.
I've verified that the gesture recognizer is being added by stepping through the portion of the code where it's actually added.
The view controller that contains this view does not have any gesture recognizers attached to it, but does implement touchesBegan, touchesMoved, and touchesEnded. However, according to this question, that the view controller implements those shouldn't affect the gesture recognition of the view.
Edit 2:
I've verified that there are no other views blocking the view with the gesture recognizer from receiving touches. I've also verified that the view is actually being added to the containing view controller's view. It seems like the problem's elsewhere.
Your set up should work, so I would guess that one of two things is happening. You may have forgotten to actually add this view to the ViewController's view, or there maybe another view placed on top of this view which is stealing the touches?
Another possibility causing the problem is that the view on which the UITapGestureRecognizer is added is not big enough, so when you tap on the screen, the touch point is not located in the view's bounds.
Also make sure the UIView is not transparent. Set the background color to e.g. black. This solved the problem for me.

Tap gesture recognizer on image throws error when passing event

I have a UIImageView inside of a custom cell. I am creating a custom tap gesture recognizer for when the UIImageView is tapped to load another detail view.
The tap gesture is set up like so:
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(imageTapped:event:)];
tapRecognizer.cancelsTouchesInView = YES;
tapRecognizer.numberOfTapsRequired = 1;
tapRecognizer.delegate = (id)self;
[cell.userImage addGestureRecognizer:tapRecognizer];
cell.userImage.userInteractionEnabled = YES;
I am using imageTapped:event: so that I can detect what cell the user is tapping and load the data accordingly. The problem is I get this error:
If I get rid of event like so, its works perfectly with no issues.
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(imageTapped:)];
I have used this method before in another application but for some reason I cannot get it to work properly with this error. Anyone recognize what this is? Thanks!
You need to read the documentation on UIGestureRecognizer...
A gesture recognizer has one or more target-action pairs associated
with it. If there are multiple target-action pairs, they are discrete,
and not cumulative. Recognition of a gesture results in the dispatch
of an action message to a target for each of those pairs. The action
methods invoked must conform to one of the following signatures:
- (void)handleGesture;
- (void)handleGesture:(UIGestureRecognizer *)gestureRecognizer;
Using one of these signatures will prevent the crash as you have already observed. You could then access the image view that was involved in the gesture by inspecting the recognizer's view property, and from this you will be able to access the appropriate UITableView cell.

Resources