I have a view hierarchy that looks something like this:
UIView (A)
UIView > UIImageView
UIView > UIView (B)
UIView > UIView (B) > Rounded Rect Button
UIView > UIView (B) > UIImageView
UIView > UIView (B) > UILabel
I've attached gesture recognizer(s) to my UIView (B). The problem that i'm facing is that i don't get any actions for the Rounded Rect Button which is inside the UIView (B). The singleTap gesture recognizer captures/overrides the button's Touch Up Inside event.
How can i make it work? I thought that the responder chain hierarchy will make sure that the button touch event will be given preference, and it WILL get triggered! What am i missing?
Here's some related code:
#pragma mark -
#pragma mark View lifecycle (Gesture recognizer setup)
- (void)viewDidLoad {
[super viewDidLoad];
// double tap gesture recognizer
UITapGestureRecognizer *dtapGestureRecognize = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleTapGestureRecognizer:)];
dtapGestureRecognize.delegate = self;
dtapGestureRecognize.numberOfTapsRequired = 2;
[self.viewB addGestureRecognizer:dtapGestureRecognize];
// single tap gesture recognizer
UITapGestureRecognizer *tapGestureRecognize = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(singleTapGestureRecognizer:)];
tapGestureRecognize.delegate = self;
tapGestureRecognize.numberOfTapsRequired = 1;
[tapGestureRecognize requireGestureRecognizerToFail:dtapGestureRecognize];
[self.viewB addGestureRecognizer:tapGestureRecognize];
// add gesture recodgnizer to the grid view to start the edit mode
UILongPressGestureRecognizer *pahGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPressGestureRecognizerStateChanged:)];
pahGestureRecognizer.delegate开发者_开发百科 = self;
pahGestureRecognizer.minimumPressDuration = 0.5;
[self.viewB addGestureRecognizer:pahGestureRecognizer];
[dtapGestureRecognize release];
[tapGestureRecognize release];
[pahGestureRecognizer release];
}
#pragma mark -
#pragma mark Button actions
- (IBAction)buttonTouchUpInside:(id)sender {
NSLog(@"%s, %@", __FUNCTION__, sender);
}
#pragma mark -
#pragma mark Gesture recognizer actions
- (void)singleTapGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer {
NSLog(@"%s", __FUNCTION__);
}
- (void)doubleTapGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer {
NSLog(@"%s", __FUNCTION__);
}
- (void)longPressGestureRecognizerStateChanged:(UIGestureRecognizer *)gestureRecognizer {
switch (gestureRecognizer.state) {
case UIGestureRecognizerStateEnded: {
NSLog(@"%s", __FUNCTION__);
break;
}
default:
break;
}
}
In the "shouldReceiveTouch" method you should add a condition that will return NO if the touch is in the button.
This is from apple SimpleGestureRecognizers example.
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
// Disallow recognition of tap gestures in the segmented control.
if ((touch.view == yourButton)) {//change it to your condition
return NO;
}
return YES;
}
hope it will help
Edit
As Daniel noted you must conform to UIGestureRecognizerDelegate
for it to work.
shani
I also had the same problem , then i tried with
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch{
if ([touch.view isKindOfClass:[UIButton class]]) { //change it to your condition
return NO;
}
return YES;
}
It is working now perfectly.........
Generally speaking, we use below delegate method to avoid the touch in all kinds of UIControls:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
if (([touch.view isKindOfClass:[UIControl class]])) {
return NO;
}
return YES;
}
Note: DO NOT do this check (check the recognizer.view class type) the gestureRecognizerShouldBegin, it won't work.
Here is a Swift 3.0 version:
extension UIViewController: UIGestureRecognizerDelegate {
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
if touch.view is UIButton {
return false
}
return true
}
Don't forget to:
Make your tapper object delegate to self (e.g: tapper.delegate = self)
The best solution is to my mind using code snippet below:
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
CGPoint touchLocation = [touch locationInView:self.view];
return !CGRectContainsPoint(self.menuButton.frame, touchLocation);
}
Here is a Swift version:
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
if (touch.view!.isKindOfClass(UIButton)) {
return false
}
return true
}
Don't forget to:
- Make you class conform to
UIGestureRecognizerDelegate
- Make your tapper object delegate to self (e.g:
tapper.delegate = self
)
for Swift 5 inside the delegate you can just add:
return (touch.view is UIButton) ? false : true
Swift version of Vivienne answer:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
let location = touch.location(in: view)
return !someView.frame.contains(location)
}
My case was the following :
UIView (with tap gesture)
├-> UIView (named "textFieldView")
├-> UIControl
├-> UITextField
├-> UIView (over the textField, with a tap gesture)
╭──────────────────────────────╮
│ ╭──────────────────────────╮ │
│ │ ╭──╮ ┌╴╶╴╶╴╶╴╶╴╶╴╶╴╶╴╶┐ │ │
│ │ ╰──╯ └╴╶╴╶╴╶╴╶╴╶╴╶╴╶╴╶┘ │ │
│ ╰──────────────────────────╯ │
└──────────────────────────────┘
The tapGesture on the upper UIView was causing a conflict with the UIControl target. The target was called if the target was setup with touchDown
(but not called for touchUpInside
) or if I was changing the inheritance from UIControl
to UIButton
while keeping the target setup on event touchUpInside
.
What I do to solve that is :
upper view is setup as delegate of its tap gesture.
Function func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool
is implemented (in upper UIView) like that :
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
let touchLocationRelatedToTextFieldView = touch.location(in: textFieldView)
return textFieldView.frame.contains(touchLocationRelatedToTextFieldView) == false
}
With that delegate function implemented I'm now able to keep my UIControl and the target setup on touchUpInside
! :)
精彩评论