Reputation: 6147
I have view with a UITapGestureRecognizer
. So when I tap on the view another view appears above this view. This new view has three buttons. When I now press on one of these buttons I don't get the buttons action, I only get the tap gesture action. So I'm not able to use these buttons anymore. What can I do to get the events through to these buttons? The weird thing is that the buttons still get highlighted.
I can't just remove the UITapGestureRecognizer after I received it's tap. Because with it the new view can also be removed. Means I want a behavior like the fullscreen vide controls.
Upvotes: 196
Views: 80759
Reputation: 537
Another option is to attach the UITapGestureRecognizer
to some subView that is underneath the UIButton
, instead of attaching to your main viewController view
Say you have a subView mySubView
and teh buttons are stacked over it, but do not belong to it. Then you will create your tapGestureRecognizer as follows
var tapGetureRecognizer = UITapGestureRecognizer()
tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(theActionToBeTriggered))
tapGestureRecognizer.numberOfTapsRequired = 1
mySubView.addGestureRecognizer(tapGestureRecognizer)
Upvotes: 0
Reputation: 89
Swift 5
Button on superview with tapgesture
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
if let _ = touch.view as? UIButton { return false }
return true
}
In my case implementing hitTest worked for me. I had collection view with button
This method traverses the view hierarchy by calling the point(inside:with:)
method of each subview to determine which subview should receive a touch event. If point(inside:with:)
returns true, then the subview’s hierarchy is similarly traversed until the frontmost view containing the specified point is found.
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
guard isUserInteractionEnabled else { return nil }
guard !isHidden else { return nil }
guard alpha >= 0.01 else { return nil }
guard self.point(inside: point, with: event) else { return nil }
for eachImageCell in collectionView.visibleCells {
for eachImageButton in eachImageCell.subviews {
if let crossButton = eachImageButton as? UIButton {
if crossButton.point(inside: convert(point, to: crossButton), with: event) {
return crossButton
}
}
}
}
return super.hitTest(point, with: event)
}
Upvotes: 7
Reputation: 11860
Optimizing cdasher's answer, you get
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldReceiveTouch:(UITouch *)touch
{
return ![touch.view isKindOfClass:[UIControl class]];
}
Upvotes: 1
Reputation: 12904
Here's the Swift version of Lily Ballard's answer that worked for me:
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
if (scrollView.superview != nil) {
if ((touch.view?.isDescendantOfView(scrollView)) != nil) { return false }
}
return true
}
Upvotes: 3
Reputation: 2220
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:.....
Upvotes: 12
Reputation: 27598
These answers were incomplete. I had to read multiple posts as to how to use this boolean operation.
In your *.h file add this
@interface v1ViewController : UIViewController <UIGestureRecognizerDelegate>
In your *.m file add this
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
NSLog(@"went here ...");
if ([touch.view isKindOfClass:[UIControl class]])
{
// we touched a button, slider, or other UIControl
return NO; // ignore the touch
}
return YES; // handle the touch
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
//tap gestrure
UITapGestureRecognizer *tapGestRecog = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(screenTappedOnce)];
[tapGestRecog setNumberOfTapsRequired:1];
[self.view addGestureRecognizer:tapGestRecog];
// This line is very important. if You don't add it then your boolean operation will never get called
tapGestRecog.delegate = self;
}
-(IBAction) screenTappedOnce
{
NSLog(@"screenTappedOnce ...");
}
Upvotes: 8
Reputation: 2508
Found this answer here: link
You can also use
tapRecognizer.cancelsTouchesInView = NO;
Which prevents the tap recognizer to be the only one to catch all the taps
UPDATE - Michael mentioned the link to the documentation describing this property: cancelsTouchesInView
Upvotes: 108
Reputation: 124
You have a simple view and some UIButtons,UITextField controls added as subviews to that view. Now you want to dismiss the keyboard when you touch anywhere else on the view except on the controls(subviews you added)
Add the following method to your XYZViewController.m(which has your view)
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[self.view endEditing:YES];
}
Upvotes: 1
Reputation: 4799
You can stop the UITapGestureRecognizer from cancelling other events (such as the tap on your button) by setting the following boolean:
[tapRecognizer setCancelsTouchesInView:NO];
Upvotes: 1
Reputation: 2403
As a follow up to Kevin Ballard's answer, I had this same problem and ended up using this code:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
if ([touch.view isKindOfClass:[UIButton class]]){
return NO;
}
return YES;
}
It has the same effect but this will work on any UIButton at any view depth (my UIButton was several views deep and the UIGestureRecognizer's delegate didn't have a reference to it.)
Upvotes: 73
Reputation: 3073
As a follow up to Casey's follow up to Kevin Ballard's answer:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
if ([touch.view isKindOfClass:[UIControl class]]) {
// we touched a button, slider, or other UIControl
return NO; // ignore the touch
}
return YES; // handle the touch
}
This basically makes all user input types of controls like buttons, sliders, etc. work
Upvotes: 159
Reputation: 71
Found another way to do it from here. It detects the touch whether inside each button or not.
(1) pointInside:withEvent: (2) locationInView:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldReceiveTouch:(UITouch *)touch {
// Don't recognize taps in the buttons
return (![self.button1 pointInside:[touch locationInView:self.button1] withEvent:nil] &&
![self.button2 pointInside:[touch locationInView:self.button2] withEvent:nil] &&
![self.button3 pointInside:[touch locationInView:self.button3] withEvent:nil]);
}
Upvotes: 7
Reputation: 185643
You can set your controller or view (whichever creates the gesture recognizer) as the delegate of the UITapGestureRecognizer
. Then in the delegate you can implement -gestureRecognizer:shouldReceiveTouch:
. In your implementation you can test if the touch belongs to your new subview, and if it does, instruct the gesture recognizer to ignore it. Something like the following:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
// test if our control subview is on-screen
if (self.controlSubview.superview != nil) {
if ([touch.view isDescendantOfView:self.controlSubview]) {
// we touched our control surface
return NO; // ignore the touch
}
}
return YES; // handle the touch
}
Upvotes: 263