I am making an iPad app where you can swipe/pan with 3 fingers at any time on the whole screen (cancel action)
It's working with a UISwipeGestureRecognizer or UIPanGestureRecognizer but the subviews underneath my fingers (for instance a UITableView or UIScrollview) received the touch and moves. Which I don't want.
My idea was to put a transparent UIView on top of the whole app that will forward touches to the other views or not. I tried something with hitTest but I think I didn't understand it well since the return number of touches isn't right and takes time...
Thanks a lot if you can help me with that :)
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
if (event.type == UIEventTypeTouches) {
if (event.allTouches.count >= 2) {
return self;
}
}
return [super hitTest:point withEvent:event];
}
I think you must use the
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
delegate of UIGestureRecognizer to define which gestures is valid in the same time.
E.g:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
NSLog(#"gestRecogn: %# otherGestRec: %#",[[gestureRecognizer class] className],[[otherGestureRecognizer class] className]);
if ([[[gestureRecognizer class] className] isEqualToString:#"UIScrollViewPanGestureRecognizer"] && [[[otherGestureRecognizer class] className] isEqualToString:#"UILongPressGestureRecognizer"]) {
return FALSE;
}
if ([[[gestureRecognizer class] className] isEqualToString:#"UILongPressGestureRecognizer"] && [[[otherGestureRecognizer class] className] isEqualToString:#"UIScrollViewPanGestureRecognizer"]) {
return FALSE;
}
return TRUE;
}
Related
Ok, I have a WKWebView with a big textarea inside it, and when I click on it, it fires this UIWebTouchEventsGestureRecognizer and shows the keyboard.
This behavior is correct. Problem is, when scrolling or panning from any other gesture, this UIWebTouchEventsGestureRecognizer is fired and the keyboard shows.
Is there any way to prevent that?
I'm using this to get the problematic gesture recognizer:
for (UIView* subview in self.subviews) {
// here comes the tricky part, desabling
for (UIView* subScrollView in subview.subviews) {
if ([subScrollView isKindOfClass:NSClassFromString(#"WKContentView")]) {
for (UIGestureRecognizer* gesture in [subScrollView gestureRecognizers]) {
if ([gesture isKindOfClass:NSClassFromString(#"UIWebTouchEventsGestureRecognizer")]) {
gesture.delegate = self;
gesture.delaysTouchesBegan = true;
gesture.delaysTouchesEnded = true;
[gesture requireGestureRecognizerToFail:scrollGesture];
}
}
}
}
}
requireGestureRecognizerToFail: doesn't work;
-(BOOL)gestureRecognizer:shouldRequireFailureOfGestureRecognizer:
also didn't work
The only thing that worked is returning false on this delegate method:
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
return false;
}
But obviously, now the keyboard will never show..
Resuming, I need the webview to get focus on taps, and to ignore pans and other gestures.
Any tips?
Did you this this one ?
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
All.
In my iOS 8.0 app.
In a Parent Child View architecture. By this code...
[self addChildViewController:slideTableViewController];
[self.view addSubview:slideTableViewController.view];
[slideTableViewController didMoveToParentViewController:self];
I have implemented TapGesturerecognizer & PanGesturerecognizer on Base View Controller.
So, that it can recognise Pan(Dragging) and Tap. I need both gestures on my BaseView.
Just do not want Tap Gesture on SlideView.
As I want to execute didSelectRowAtIndexpath method on child view,
Solution:
Answer for Question 1:
Many StackOverflow answers have the same funda..
Disable Tap gesture when your touch encounters child view.
if([gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]]){
if(CGRectContainsPoint(slideTableViewController.view.frame,[touch locationInView:self.view])){
return NO;
}
}
Answer for Question 2:
How to determine Is it a PanGesture or TapGesture ?
For each gesture type the delegate method will call
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
If you have implemented two gestures in your view, pan gesture and touch gesture,
This method will call 2 times, 1 time for pan gesture and 1 time for tap gesture.
So, in this method you can check like isKindOfClass method.
Thank you very much for helping......
Just set tapGesture.cancelsTouchesInView = YES;
You can implement the gesture's delegate method in your baseViewController :
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizershouldReceiveTouch:(UITouch *)touch {
return touch.view == self.view;
}
OR
//If it is a Tap Gesture and Your touch intersects with a perticular View, For that we have this method.
if([gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]]){
if(CGRectContainsPoint(slideTableViewController.view.frame,[touch locationInView:self.view])){
return NO;
}
}
I have done this in UIViews I hope this could help you:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
[touch locationInView:self.view];
if(touch.view == self.topView )
{
//just return
}
if(touch.view == SOSTopView )
{
//just return
}
}
Looks like you should implement this method of UITableViewDelegate:
- (NSIndexPath *)tableView:(UITableView *)tableView
willSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// you can add some logic here
return nil;
}
This will prevent from selecting any rows.
Isn't there a userInteractionEnabled property that you can make use of? So you could do the following:
slideTableViewController.userInterationEnabled = NO;
This is what I want to do
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
if (touch.view != self.myView) {
NSLog(#"we went with no");
//don't let anything that's not myView fire the gesture recognizer
return NO;
}
NSLog(#"we went with yes");
return YES;
}
The problem is the code always chooses the path of "we went with no" no matter where I actually tap. How do I fix my if check?
If you're finding that your gesture recognizer action is firing when you're tapping outside of a specific view, you should double-check that you added the gesture recognizer correctly.
[self.myView addGestureRecognizer:[[UIGestureRecognizer alloc] initWithTarget:self action:#selector(someAction:)]];
If you still experience incorrect action fires, you could check the touch location.
if (CGRectContainsPoint(self.myView.bounds, [gesture locationInView:self.myView]))
I have a clear UIView which has gesture recognizers attached to it.
This clear uiview covers the entire super view to allow for the gestures to be invoked from anywhere on it.
Under this clear UIView sit different components such as tables,buttons,collectionview etc.
The clear UIView has no idea what is under it any time.
What I want - if a view which is under the clear uiview can handle a touch event (or any type of gesture) - the clear view should disregard that event - and the event will pass through to the underlying view which could handle it.
I tried
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
but I don't know how to make sure the underlying view can handle it.
-(id)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
id hitView = [super hitTest:point withEvent:event];
if (hitView == self)
{
return nil;
}
else
{
return hitView;
}
}
Add this to your to clear view.
If the hit on clear view means just return nil.
You can override pointInside: withEvent: method. This method returns a boolean value indicating whether the receiver contains the specified point. So if we return NO then your upper clear view will become transparent for touch events and they will be passed to underlying views.
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
// Clear UIView will now respond to touch events if return NO:
return NO;
}
use below code for your case->
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
UIView *hitTestView = [super hitTest:point withEvent:event];
if(hitTestView!=nil){
//check for gesture
if([hitTestView.gestureRecognizers count]>0)
return hitTestView;
//if it is subclass of UIControl like UIButton etc
else if([hitTestView isKindOfClass:[UIControl class]])
return hitTestView;
//if can handle touches
else if([hitTestView respondsToSelector:#selector(touchesBegan:withEvent:)])
return hitTestView;
else
return nil;
}
else{
return self;
}
}
In the above code if the subView which is hitView can anyway handle touch ,we return that object to handle that touch. If there is no such hitTest view, then we return the view itself.
I used some of these suggestions and used the following solution:
I added the gesture recognizer to the bottom most superview in the heirarchy (and not the top most)
Then in that class over rid
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
UIView *v = [super hitTest:point withEvent:event];
// if v is nil then touch wasn't in this view or its subviews
if (v == nil)
{
return nil;
}
// in any case if the topview was hidden than return the default value
if (self.myTopView.hidden)
{
return v;
}
// if the view isn't hidden but the touch returned a control - than we can pass the touch to the control
if ([v isKindOfClass:[UIControl class]])
{
return v;
}
// decide on what threshold to decide is a touch
CGFloat threshHold = 40;
// if the touch wasn't on a control but could initiate a gesture than that view should get the touch
if (v.gestureRecognizers)
{
threshHold = 30;
// return v;
}
// check if the threshold should be bigger
if ([self someCondition])
{
threshHold = 100;
}
// threshold according to its position - this is the dynamic part
if (point.y > (self.myTopView.frame.origin.y - threshold))
{
return self.handleBarView;
}
return v;
}
it means user can't pan and pinch simultaneously and pinch gesture stops pan one.
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
This code is not working for me because it makes these gestures working simultaneously.
By default if I don't use this code then pan gesture stops pinch one but I need the opposite thing.
Updated
#interface SomeClass : UIViewController <UIGestureRecognizerDelegate>
...
#end
#implementation SomeClass
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
#end
You can create dependencies between recognizers using the requireGestureRecognizerToFail: method so that one gesture will only become eligible to begin when another has failed.
Depending on exactly what you need you may have to subclass and create your own gestures so that you can control how and when they begin and fail.
solved by editing pan gesture handler:
- (IBAction)panGRUsed:(id)sender {
UIPanGestureRecognizer *gr = (UIPanGestureRecognizer *)sender;
if (gr.numberOfTouches > 1) {
[gr setTranslation:CGPointZero inView:self.view];
} else {
...
}
}