I'm working on implementing a UITextView in which I only want to respond to touches that are in a specified part of the text view.
I have a gesture recognizer attached to the view, and that works fine, until I set the view to become first responder, which I do if the tap's point in the view is greater then an X an Y value.
- (IBAction)textViewTapped:(UIGestureRecognizer *)sender {
CGPoint point = [sender locationOfTouch:0 inView:self.view];
NSLog(#"x ix %f, y is %f", point.x, point.y);
if (point.x > 96 && point.y > 106)
[self.myTextView becomeFirstResponder];
}
The problem is, once it is set to be first responder, and then resigned by tapping outside of that text view, my gesture recognizer method is never called again. If I tap in the area that doesn't set first responder, then my method gets called as many times as I tap. If I set and then resign first responder, it doesn't respond after the first time it is resigned.
- (IBAction)viewTapped:(UIGestureRecognizer *)sender {
[self.view endEditing:YES];
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(textViewTapped:)];
[self.myTextView addGestureRecognizer:tap];
NSArray *gestures = [self.myTextView gestureRecognizers];
NSLog(#"got %d recognizers", [gestures count]);
}
In just trying things, if I add a new gesture recognizer after each resign, then that works, but is obviously not a good solution.
Any thoughts?
I have the same problem, and i solved it by implementing dummy UIGestureRecognizerDelegate
add this to your code
myGestuerRecognizer.delegate = self
then implement UIGestureRecognizerDelegate
#pragma mark - UIGestureRecognizerDelegate
-(BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer{
return YES; }
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer
*)otherGestureRecognizer{
return YES; }
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch{
return YES; }
this works for me
iOS 10 Swift 3.0
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
Related
I am adding two gesture on a view. One is tap gesture and second is swipe gesture. Whenever user tries to swipe the view , tap gesture method gets called (tap gesture gets detected). What could be the reason ? Tap gesture should get ignore in this case.
below is the code .
UISwipeGestureRecognizer *swipeGest = [[UISwipeGestureRecognizer alloc]initWithTarget:self action:#selector(itemSwiped:)];
swipeGest.direction = UISwipeGestureRecognizerDirectionUp ;
[messageView addGestureRecognizer:swipeGest];
UITapGestureRecognizer *gest = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(itemSelected:)];
[messageView addGestureRecognizer:gest];
use this delegate method to avoid other gesture
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
if ([gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]]) {
[otherGestureRecognizer requireGestureRecognizerToFail:gestureRecognizer];
return false;
}else if([gestureRecognizer isKindOfClass:[UISwipeGestureRecognizer class]]){
[otherGestureRecognizer requireGestureRecognizerToFail:gestureRecognizer];
return false;
}
return true;
}
Because your using multiple gesture on same view. this may over hide one another property .For Ex: first gesture recognizer recognizes the gesture (tap in this case) it cancels all touch events. Therefore the remaining gesture recognizers will never finish recognition, and will never fire their events.
In order to add more than one UIGestureRecognizer onto the same view, you need to set the delegate property (UIGestureRecognizerDelegate)
yourGesture.delegate = self
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
if (gestureRecognizer isKindOfClass: [UISwipeGestureRecognizer class] || gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]) {
return YES
} else {
return NO
}
}
Try this;
UISwipeGestureRecognizer *swipeGest = [[UISwipeGestureRecognizer alloc]initWithTarget:self action:#selector(itemSwiped:)];
swipeGest.direction = UISwipeGestureRecognizerDirectionUp ;
[messageView addGestureRecognizer:swipeGest];
UITapGestureRecognizer *gest = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(itemSelected:)];
gest.numberOfTapsRequired = 1;
[messageView addGestureRecognizer:gest];
-(void)itemSwiped:(UISwipeGestureRecognizer*)gesture
{
NSLog(#"swipe");
}
-(void)itemSelected:(UITapGestureRecognizer*)gesture
{
NSLog(#"tap");
}
Change single tap to doubletap gesture. so you can be sure user only double tap or swipe. It is good advice from me please follow it.
Please help me with the below scenario.
Self.view (Swipe Gesture added here)
UicollectionView Object in subView (Default pan,swipe gestures are there)
Want to disable scrollview scrolling/failCollectionViewPan gesture for 2 finger swipe event
Means collection view will not scroll if 2 finger swipe executed
Another approach can be to disable Collection view scrolling while 2 fingers used. Over here, I want not to swipe collection view on 2 finger swipe.
I have implemented this code, but its slowing down scrolling.
[self.collectionView.panGestureRecognizer shouldBeRequiredToFailByGestureRecognizer:_swipeL];
Then I have implemented below codes
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer{
return YES;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
return YES;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch{
return YES;
}
Now both, Swipe and Scroll are working together.
Then, I have tried in a below manner, but still not got fixed.
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
if([gestureRecognizer isKindOfClass:[UISwipeGestureRecognizer class]]){
if(gestureRecognizer.numberOfTouches==2){
if( [[otherGestureRecognizer.view class] isSubclassOfClass:[UITableViewCell class]] ||
[NSStringFromClass([otherGestureRecognizer.view class]) isEqualToString:#"UITableViewCellScrollView"] ||
[NSStringFromClass([otherGestureRecognizer.view class]) isEqualToString:#"UITableViewWrapperView"] || [NSStringFromClass([otherGestureRecognizer.view class]) isEqualToString:#"UIScrollViewPanGestureRecognizer"] || [NSStringFromClass([otherGestureRecognizer.view class]) isEqualToString:#"UIScrollViewPagingSwipeGestureRecognizer"])
{
NSLog(#"Allow&Disable %#", [otherGestureRecognizer description]);
[gestureRecognizer requireGestureRecognizerToFail:otherGestureRecognizer];
return NO;
}
}
}
return YES;
}
Also I have implemented below 2 methods to fix it but fails.
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer NS_AVAILABLE_IOS(7_0);
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer NS_AVAILABLE_IOS(7_0);
Its is able to fix for ScrollView with such ways, but the same ways are not working for UICollectionView. Because of, colection view's default pan gesture, it cannot be able to modified. While tring this way, app crashed.
If you want to detect pan gesture, try something like this. But you need to recognise direction of pan gesture:
- (void)viewDidLoad
{
[super viewDidLoad];
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(panGesture:)];
panGesture.delegate = self;
[self.yourCollectionView addGestureRecognizer:panGesture];
}
- (void)panGesture:(UIPanGestureRecognizer *)gesture;
{
if (gesture.numberOfTouches == 2) {
NSLog(#"Two fingers pan gesture");
}
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]
&& gestureRecognizer.numberOfTouches == 2) {
return NO;
}
return YES;
}
Your problem with UISwipeGestureRecognizer is, that it fires later than UIPanGesture, so in the shouldRecognizeSimultaneouslyWithGestureRecognizer delegate call, swipe gesture is always otherGestureRecognizer, and UIPanGesture is always gestureRecognizer, and in this function you can only disable otherGestureRecoginzer...
UPDATE:
Another solution: use another UIPanGestureRecognizer to disable scroll pan gesture:
- (void)viewDidLoad
{
[super viewDidLoad];
UISwipeGestureRecognizer *swipeGesture = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(swipeGesture:)];
swipeGesture.direction = UISwipeGestureRecognizerDirectionDown;
swipeGesture.delegate = self;
swipeGesture.numberOfTouchesRequired = 2;
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:nil];
panGesture.delegate = self;
[self.tableView addGestureRecognizer:panGesture];
[self.tableView addGestureRecognizer:swipeGesture];
}
- (void)swipeGesture:(UIPanGestureRecognizer *)gesture;
{
NSLog(#"Two fingers swipe gesture");
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]
&& gestureRecognizer.numberOfTouches == 2) {
return NO;
}
return YES;
}
This answer might be late, but I did have a similar problem today and couldn't find a solution, until I figured something out myself. It's quite simple, actually.
Swift 5
We create a gesture
let gesture = UIPanGestureRecognizer(target: self, action: #selector(functionCall))
gesture.minimumNumberOfTouches = 2
gesture.maximumNumberOfTouches = 2
We then assign that gesture to the collectionView, effectively overwriting the scroll gesture on it.
collectionView.addGestureRecognizer(gesture) // simply overwrites the 2 finger gesture on the collectionView
It now is possible to scroll the collectionView with 1, 3 or more fingers, but the 2 finger pan gesture is blocked.
We currently are using a solution similar to the one mentioned here (see Ares's answer). This does not seem to work in iOS8.
I have a form sheet, and I want to dismiss it as soon as the user taps on the dimmed view 'behind' the form sheet.
Previously, this seemed possible by adding a gesture recogniser to the window, and checking tap-location to see if it was outside the current form sheet;
I also noticed the point needs to be converted (switch x and y) of the device is used in landscape mode. Other than that, right now it only receives gestures that occurred from inside the form sheet, where before any tap gesture anywhere on the screen would trigger an event.
- (void)viewDidLoad
{
[super viewDidLoad];
self.recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTapBehind:)];
self.recognizer.numberOfTapsRequired = 1;
self.recognizer.cancelsTouchesInView = NO;
[self.view.window addGestureRecognizer:self.recognizer];
}
- (void)handleTapBehind:(UITapGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateEnded)
{
CGPoint location = [sender locationInView:nil];
if (UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation) && IOS8)
{
location = CGPointMake(location.y, location.x);
}
// if tap outside pincode inputscreen
BOOL inView = [self.view pointInside:[self.view convertPoint:location fromView:self.view.window] withEvent:nil];
if (!inView)
{
[self.view.window removeGestureRecognizer:sender];
[self dismissViewControllerAnimated:YES completion:nil];
}
}
}
As mentioned in the thread you referenced, you should add a UIGestureRecognizerDelegate and implement the methods:
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
return YES;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
return YES;
}
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 using UIPageController to implement a page based navigation application. I would like to enable the swipe gestures only at the margin of the page and prevent that from the inner content view. The page has a margin of 20 pixels:
contentView.frame = CGRectInset(self.view.frame, 20, 20);
Upon recognising a gesture you should be able to retrieve the locationInView and then if this is an acceptable value proceed, otherwise not.
First add in the class interface.
#interface MyPageViewController :UIViewController<UIPageViewControllerDelegate, UIGestureRecognizerDelegate>
Then in viewDidLoad add
for (UIGestureRecognizer *recognizer in self.pageViewController.gestureRecognizers) {
recognizer.delegate = self;
}
Then implement shouldReceiveTouch method
#pragma mark - UIGestureRecognizer delegate methods
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch{
CGPoint touchPoint = [touch locationInView:self.pageViewController.view];
CGRect innerRect = CGRectInset(self.pageViewController.view.frame, 40, 40);
if (CGRectContainsPoint(innerRect, touchPoint)) {
return NO;
}
return YES;
}