I am using UITapGestureRecognizer for detecting which UIView was tapped on my screen but for some reason it only detects the parent view tap, for example below code logs only parent views tag. How do i detect subview taps which are present on main view. Please suggest.
Inside View did load :-
UITapGestureRecognizer *viewTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(actionForViewTapped:)];
[self.view addGestureRecognizer:viewTapRecognizer];
Method outside view did load.
-(void) actionForViewTapped:(UITapGestureRecognizer*)sender {
NSLog(#"view tapped");
UIView *view = sender.view;
NSLog(#"view tag is %lu", view.tag); //Always prints parent view tag.
if(view.tag == 10){
NSLog(#"tag1 tapped"); //Not called
}
if(view.tag == 20){
NSLog(#"tag 2 tapped"); //Not called
}
}
We have more options to find detecting on sub view by tap gesture
CHOICE 1:Directly tap to SubView
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(tapSubView)];
tapGesture.numberOfTapsRequired = 1;
[subView addGestureRecognizer:tapGesture];
CHOICE 2:Finding tap on SubView through Parent View
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(tapSubView)];
tapGesture.numberOfTapsRequired = 1;
[self.view addGestureRecognizer:tapGesture];
-(void)tapSubView:(UITapGestureRecognizer *)sender
{
UIView* view = sender.view;
CGPoint loc = [sender locationInView:view];
UIView* subview = [view hitTest:loc withEvent:nil];
//OR
CGPoint point = [sender locationInView:sender.view];
UIView *viewTouched = [sender.view hitTest:point withEvent:nil];
if ([viewTouched isKindOfClass:[self.view class]])
{
NSLog(#"the subView is called");
}
else
{
NSLog(#"the subView is not called");
}
}
Printed Output is
the subView is called
CHOICE 3:Find Tap Detection using Delegate methods of Gesture
First You have to add the GestureRecognizerDelegate
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
if([touch.view isKindOfClass: [self.view class]] == YES)
{
return YES; // return YES (the default) to allow the gesture recognizer to examine the touch object
}
else {
return NO; //NO to prevent the gesture recognizer from seeing this touch object.
}
}
The gesture recognizer is only associated with one specific view, which means it will only recognize touches on the view it is added to. If you want to know which subview was touched, then you will need to do a couple of things:
Set userInteractionEnabled = false for each subview. This will make it so that every touch on a subview is passed up to the parent view, and the touch will be recognized by the gesture recognizer.
There isn't enough information on your view hierarchy or layout to know exactly how to proceed from here, but you can use one or some of these methods to determine which view was touched: UIView.hitTest(_:with:), UIView.point(inside:with:), CGRectContainsPoint() or UIGestureRecognizer.location(in:). For example, if the subviews do not overlap each other, you could use the following code snippet to test if the touch was in a particular view:
let location = tapGesture.locationInView(parentView)
if CGRectContainsPoint(subview1, location) {
// subview1 was touched
}
Related
I am using UITapGestureRecognizer for detecting which UIView was tapped on my screen but for some reason it only detects the parent view tap, for example below code logs only parent views tag. How do i detect subview taps which are present on main view. Please suggest.
Inside View did load :-
UITapGestureRecognizer *viewTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(actionForViewTapped:)];
[self.view addGestureRecognizer:viewTapRecognizer];
-(void) actionForViewTapped:(UITapGestureRecognizer*)sender {
NSLog(#"view tapped");
UIView *view = sender.view;
NSLog(#"view tag is %lu", view.tag); //Always prints parent view tag.
if(view.tag == 10){
NSLog(#"tag1 tapped"); // not called
}
if(view.tag == 20){
NSLog(#"tag 2 tapped"); // not called
}
}
You can detect subview using following code, I just tested creating sample project.
-(void) actionForViewTapped:(UITapGestureRecognizer*)sender {
NSLog(#"view tapped");
UIView *view = sender.view;
CGPoint loc = [sender locationInView:view];
UIView* subview = [view hitTest:loc withEvent:nil];
NSLog(#"view tag is %lu", subview.tag); //will print Subview tag.
if(view.tag == 10){
NSLog(#"tag1 tapped");
}
if(view.tag == 20){
NSLog(#"tag 2 tapped");
}
}
A UIGestureRecognizer is to be used with a single view.
If you want to use UIGestureRecognizer you will have to create one for each view, calling the same method.
I have a ParentViewController whose view has a subview. I want ParentViewController to have the initial opportunity to evaluate gestures in its view, and only upon failure of those gestures should the subview then have the chance to evaluate the gesture. But instead things seem to be going the opposite direction, and the subview is actually evaluating a pinch gesture that belongs to the parent view. Here's how things are set up:
ParentViewController has a pinch gesture recognizer whose delegate is set to ParentViewController:
pinchRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:#selector(handlePinch:)];
pinchRecognizer.delegate = self;
[self.view addGestureRecognizer:pinchRecognizer];
ParentViewController implements the UIGestureRecognizerDelegate protocol, specifically implementing the method gestureRecognizerShouldBegin:
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer //DEPENDENCY
{
return [gestureRecognizer isKindOfClass:[UIPinchGestureRecognizer class]];
}
ParentViewController's view has a subview. The subview has a tap gesture recognizer and a pan recognizer:
_tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(stepTapped:)];
[self addGestureRecognizer:_tapRecognizer];
_tapRecognizer.numberOfTapsRequired = 1;
_tapRecognizer.delegate = self;
_panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePan:)];
[self addGestureRecognizer:_panRecognizer];
_panRecognizer.maximumNumberOfTouches = 1;
_panRecognizer.delegate = self;
The subview also implements the UIGestureRecognizerDelegate protocol, specifically implementing the method gestureRecognizerShouldBegin:
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) {
CGPoint translation = [(UIPanGestureRecognizer *)gestureRecognizer translationInView:[self superview]];
return (fabsf(translation.x) > fabsf(translation.y) && translation.x < 0);
} else if ([gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]]) {
return YES;
}
return NO;
}
Finally, ParentViewController tells the subview's gesture recognizers to only run when its' own pinch gesture fails:
[subview.panRecognizer requireGestureRecognizerToFail:pinchRecognizer]; //GESTURE DEPENDENCY
[subview.tapRecognizer requireGestureRecognizerToFail:pinchRecognizer]; //GESTURE DEPENDENCY
Despite these last two lines of code, I find that the subview's gestureRecognizerShouldBegin: method gets called before that of ParentViewController. Furthermore, the implementation of gestureRecognizerShouldBegin: in ParentViewController won't even run unless I modify the subview's implementation to return YES if a pinch gesture is taking place (see final line):
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) {
CGPoint translation = [(UIPanGestureRecognizer *)gestureRecognizer translationInView:[self superview]];
return (fabsf(translation.x) > fabsf(translation.y) && translation.x < 0);
} else if ([gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]]) {
return YES;
}
return [gestureRecognizer isKindOfClass:[UIPinchGestureRecognizer class]];
}
With the above code, the subview's gestureRecognizerShouldBegin: runs and, upon returning YES at the end if it's a pinch gesture, ParentViewController's gestureRecognizerShouldBegin: gets called.
In case it isn't clear, the behavior I expect is that the ParentViewController's gestureRecognizerShouldBegin: always gets called first, and only upon failure will the subviews' implementation get called.
I am trying to design a view which contains a list of items to be ordered and placed into an array. The items are dynamically generated and thus cold be an amount that goes off the bottom of the screen.
For this reason my first view is a UIScrollview which takes the whole devices screen
Nested under this I have a label, explaining what the list is for and how to interact with it and then a UITableView with drag & drop with the delegate methods from http://b2cloud.com.au/how-to-guides/reordering-a-uitableviewcell-from-any-touch-point
The problem I am facing is that while the script works great when there are 1 or two rows, when the contentsize of the UIscrollview is larger than the screen it seems to take priority over the drag & drop leading to unpredictable behaviour.
Is there any way to make clicks on the table take priority to only edit the cells and allow the user to scroll by interacting elsewhere on the view?
Thanks
UPDATE
Based on the comment below I managed to get:
- (void)viewDidLoad
{
[super viewDidLoad];
//
//
//
UIPanGestureRecognizer *tapGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(wasPanned:)];
[self.view addGestureRecognizer:tapGesture];
}
-(void)wasPanned:(UIPanGestureRecognizer *)gesture
{
CGPoint point = [gesture locationInView:scrollView];
UIView *isTable = [scrollView hitTest:point withEvent:nil];
if (gesture.state == UIGestureRecognizerStateBegan)
{
if([[[isTable class] description] isEqualToString:#"UITableViewCellReorderControl"])
{
NSLog(#"Dragged from within table");
[scrollView setScrollEnabled: NO];
}
else
{
[scrollView setScrollEnabled:YES];
}
}
else{
[scrollView setScrollEnabled:YES];
}
}
Now, when the scrollview is not long enough to begin scrolling it NSLogs the message fine
However when the scrollview is longer it only recognises the gesture if the scrollview hasn't began scrolling first
Update
I now have the Console recognising touches in the table 100% of the time and disable scrolling. Disabling the scrolling however also stops the drag & drop functionality. Does anyone know why?
Extra code:
tapGesture.delegate = self;
#pragma mark UIGestureRecognizerDelegate
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
So my final (but not perfect) solution was to do this:
.h
<UIGestureRecognizerDelegate>
.m (viewDidLoad)
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(wasTapped:)];
[self.view addGestureRecognizer:tapGesture];
tapGesture.delegate = self;
.m
-(void)wasTapped:(UIPanGestureRecognizer *)gesture
{
CGPoint point = [gesture locationInView:scrollView];
UIView *isTable = [scrollView hitTest:point withEvent:nil];
if (gesture.state == UIGestureRecognizerStateBegan)
{
if([[[isTable class] description] isEqualToString:#"UITableViewCellReorderControl"])
{
NSLog(#"Dragged from within table");
[scrollView setScrollEnabled: NO];
}
else
{
[scrollView setScrollEnabled:YES];
}
}
else{
[scrollView setScrollEnabled:YES];
}
}
A tap gesture works better than a Pan gesture because a pan gesture seems to recognise press&hold, drag, stop, drag as two different gestures. The feature now works as it should but if you so much as move your finger before the animation for a cell moving has started it will scroll. You also have to wait for the view (can I call it overscroll?) animation to completely stop and the scrollbars disappear before it will grab cells.
I'd appreciate it if anyone can improve on it :)
I have a cutsom UITableViewCell implemention.
I have registered this subclass of UITableViewCell for a UIPanGestureRecognizer which i use to swiping the cells to the right or left.
// in the UITableViewCell subclass :
UIGestureRecognizer* recognizer =
[[UIPanGestureRecognizer alloc] initWithTarget:
self
action:#selector(handlePan:)];
recognizer.delegate = self;
[self addGestureRecognizer:recognizer];
recognizer.cancelsTouchesInView = NO;
Now I want to to present a view controller when the user does a two finger swipe "up"
on the screen.
So, I added a UISwipeGestureRecognizer to the tableview.
// code in the view controller containing the tableview reference.
UISwipeGestureRecognizer *swipe = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(handleViewsSwipe:)];
[swipe setDirection:UISwipeGestureRecognizerDirectionUp];
[swipe setDelaysTouchesBegan:NO];
[[self tableView ]addGestureRecognizer:swipe];
swipe.cancelsTouchesInView= YES;
[swipe setNumberOfTouchesRequired:2];
swipe.delegate = self;
self.tableView.multipleTouchEnabled = YES;
But when I do a two finger swipe on the screen, the pan gesture gets triggered .
How can I solve this ?
As sooper's says, setting the maximumNumberOfTouches = 1 will probably work.
For others trying to deal with 2 gestureRecognizers at the same time that are both 1 touch gestures I found that making sure to set this delegate to yes
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
return YES;
}
and then in the gesture recognizer action you can check for a certain translation or whatever you need and cancel one of the gesture recognizers.
Such as:
- (void)panSwipeRecognizer:(UIPanGestureRecognizer*)panRecognizer
{
CGPoint translation = [panRecognizer translationInView:self.superview];
if(panRecognizer.state == UIGestureRecognizerStateBegan)
{
if(fabsf(translation.x) < fabsf(translation.y))
{
//deactivate horizontal gesture recognizer
panRecognizer.enabled = NO;
panRecognizer.enabled = YES;
}
else //if(fabsf(translation.x) > fabsf(translation.y))
{
//deactivate vertical gesture recognizer
otherGestureRecognizer.enabled = NO;
otherGestureRecognizer.enabled = YES;
}
}
//other statements like stateChanged and stateBegan
}
I have parentView and subView childView.
childView is positioned in the middle of my parentView and is about half it's size.
I want to close childView when user tap on the parentView.
My code as follows creates a UITapGestureRecognizer in the parentView once the childView is open.
My problem is that the tap event is triggered when the user touches any view, not just the parentView.
Thus, I was wondering how can I just make the event happen if ONLY the parentView is touched or any other possible to close the sub view if the parent is touched.
- (IBAction)selectRoutine:(id)sender {
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Storyboard" bundle:nil];
createRoutinePopupViewController* popupController = [storyboard instantiateViewControllerWithIdentifier:#"createRoutinePopupView"];
popupController.view.center = CGPointMake(self.view.bounds.size.width / 2, self.view.bounds.size.height / 2);
_ass = popupController;
//Tell the operating system the CreateRoutine view controller
//is becoming a child:
[self addChildViewController:popupController];
//add the target frame to self's view:
[self.view addSubview:popupController.view];
//Tell the operating system the view controller has moved:
[popupController didMoveToParentViewController:self];
UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleSingleTap:)];
[singleTap setNumberOfTapsRequired:1];
[self.view addGestureRecognizer:singleTap];
}
-(void) handleSingleTap: (id) sender {
NSLog(#"TEST STRING");
}
You need to use UIGestureRecognizerDelegate , implement following method.
it will check the touched view.
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
if( [touch view] != popupController.view)
return YES;
return NO;
}