Reputation: 1633
I have a UIView (the 'container view') which contains several 'sub views'. I want to add a UITapGestureRecognizer to the container view, such that it is activated when I touch the region inside the container view but outside the subviews.
At the moment, touching anywhere inside the container view, including inside the subviews activates the gesture recognizer.
The implementation looks something like this: In the controller:
ContainerView *containerView = [[ContainerView alloc] initWithSubViews:array];
UITapGestureRecognizer *tap = [UITapGestureRecognizer alloc] initWithTarget:self action:@selector(someSelector)];
[containerView addGestureRecognizer:tap];
[self.view addSubView:containerView];
In ContainerView.m
-(id)initWithSubviews:(NSArray *)array {
for (subView *s in array) {
[self addSubView:s];
}
return self;
}
I think the problem occurs because the gesture recognizer is added after the subviews are. If that is true then the solution would require breaking the initWithSubViews method into two separate ones, which I would prefer to avoid.
Thank You
Upvotes: 33
Views: 19105
Reputation: 2699
updated to Swift 5
@Awais Hussain's answer is perfect for that,
view with gesture -> Controller.view -> subView -> ViewThingI_DontWantToBeTouched
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let tap = UITapGestureRecognizer(target: self, action: #selector(ViewController.tapGestureHandler(_:)))
}
@objc
func tapGestureHandler(_ sender: UITapGestureRecognizer){
let point = sender.location(in: sender.view)
if let viewTouched = sender.view?.hitTest(point, with: nil), viewTouched is ViewThingI_DontWantToBeTouched{
()
// Do nothing;
}
else {
// respond to touch action
}
}
}
Upvotes: 1
Reputation: 2396
Updated Answer for Swift 4
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
if touch.view != yourView
{
return false
}
return true
}
Before that make sure you have setting delegate property to your object ..
Upvotes: 0
Reputation: 706
Lots of answers, adding another alternate solution. Initialise the view tag of the subviews you don't want to receive touches from as 1 and do the following:
- (void)viewDidLoad {
[super viewDidLoad];
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTap:)];
tap.delegate = self;
[self.view addGestureRecognizer:tap];
}
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
if (touch.view.tag == 1) {
return NO;
}
return YES;
}
-(void)handleTap:(id)sender
{
//Handle tap...
}
This way, the touch gesture will be received only from the views you require it from.
Upvotes: 3
Reputation: 4105
Remember to add and set the delegate method -
In your UIViewController's .h file include this delegate
@interface YourViewController: UIViewController<UIGestureRecogniserDelegate>
Then where you're creating your tapGesture object, (within viewDidLoad for example)
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(actionMethodYouWantToHappenOnTap)];
tap.delegate = self; //Remember to set delegate! Otherwise the delegate method won't get called.
[self.view addGestureRecognizer:tap];
Remembering to set the delegate method tap.delegate = self;
then the delegate method for tap will now fire on tap.
Within this method you handle when you would like the tap gesture recognition to be used, in my code I wanted it to ignore a tap if a particular subview was visible
-(BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer{
if (!mySubview.hidden){
return NO; //This fired if said subview was visible, though whatever the condition of where and when you want tap to work, can be handled within this delegate method.
}
return YES;
}
I hope this helps anyone else with the same issue. Remembering to set the delegate method I found was something that's easily overlooked.
Cheers
Upvotes: 6
Reputation: 1633
I managed to get it working by doing the following:
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapGestureHandler:)];
// ...
-(void) tapGestureHandler:(UITapGestureRecognizer *)sender {
CGPoint point = [sender locationInView:sender.view];
UIView *viewTouched = [sender.view hitTest:point withEvent:nil];
if ([viewTouched isKindOfClass:[ThingIDontWantTouched class]]) {
// Do nothing;
} else {
// respond to touch action
}
}
Upvotes: 13
Reputation: 9092
I used the simple way below. It works perpectly!
Implement UIGestureRecognizerDelegate function, accept only touchs on superview, not accept touchs on subviews:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
if (touch.view != _mySuperView) { // accept only touchs on superview, not accept touchs on subviews
return NO;
}
return YES;
}
Upvotes: 43
Reputation: 535086
iOS 6 introduces a great new feature that solves this exact problem - a UIView (subview) can return NO from gestureRecognizerShouldBegin:
(gesture recognizer attached to a superview). Indeed, that is the default for some UIView subclasses with regard to some gesture recognizers already (e.g. a UIButton with regard to a UITapGestureRecognizer attached to a superview).
See my book on this topic: http://www.apeth.com/iOSBook/ch18.html#_gesture_recognizers
Upvotes: 15
Reputation: 1590
ADDED:
i just thought of something better.
in your handler
-(void)tapGestureHandler:(UITapGestureHandler)gesture
check if gesture.view is the superview. it will return the subviews if subviews are tapped.
====================================================
I would suggest overriding within the superview.
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
this is called to determine if a gesture falls within a view
see the answer to this question to see how it behaves by default.
Event handling for iOS - how hitTest:withEvent: and pointInside:withEvent: are related?
you can check if the point is in any of its subviews. if yes, return nil, else return self.
OR
in each of the subviews, add a tapgesture recognizer that does nothing. the gesture recognizer of a subview will cancel the gesture recognizer of its superview by default. i would keep an eye on the memory footprint if there are many subviews though.
Upvotes: 0
Reputation: 31311
If you added UITapGestureRecognizer
for ContainerView, it should respond with all over ContainerView. It will not mind its subviews.
Check the Gesture location, if its in your subview position,just skip the gesture actions.
- (void) tapGestureHandler:(UIGestureRecognizer *) gestureRecognizer {
CGPoint tapPoint = [gestureRecognizer locationInView:nil];
//check your tapPoint contains ur subview frame, skip.else do ur actions
}
Upvotes: 0