Handling Multiple Gestures on a View - objective-c

Is there any way to detect a single tap vs. a scrolling gesture on a UIWebView?
I have a UIWebView, that contains rich text that many times, scrolls, if there is a lot of text content. I need to add a new feature that will allow the user to tap the UIWebView to get different content.
The problem is, my solution for this was to place a clear custom button on top of the UIWebView, which handles the tap but kills the scrolling feature. How do the cool kids do this type of thing?
Thanks

I solved this with a gesture recognizer. This eliminates the need for the UIButton overlay altogether.
//Handle taps on the UIWebView
self.singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(**tapDetected:**)];
singleTap.numberOfTapsRequired = 1;
singleTap.delegate = self;
[self.readAboutItView addGestureRecognizer:singleTap];
//Set up the event handler
- (IBAction)**tapDetected:**(UIGestureRecognizer *)sender {
//Do something with the content
[self webViewTouched:self];
}
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch{
return YES;
}
-(BOOL) gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
return YES;
}

Related

iOS10 UITextView touch event crash

I have a very strange issue, UITextView touch event crash on double tap whereas same code works with < iOS10 version. (It means below iOS10 version there is no crash for press gesture recognizer)
Actually, I am adding the double tap and log press gesture based on permission. If the user has permission to comment then add gestures in viewDidLoad methods. Comment is allowed only with double tap or long press
singleTapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(singleTapGestureAction:)];
singleTapGesture.numberOfTapsRequired = 1;
// adding gesture to open window for commenting only when he has writing access
if (canComment) {
longPressgesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(longPressGestureAction:)];
longPressgesture.minimumPressDuration = 0.2;
doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(doDoubleTap:)];
doubleTap.numberOfTapsRequired = 2;
}
On single tap
-(void)singleTapGestureAction:(UITapGestureRecognizer*)tapGestureRecognizer{
if (isSingleTapped) {
isSingleTapped = NO;
return;
}
isSingleTapped = YES;
UITextView *textView = (UITextView *)tapGestureRecognizer.view;
[self.commentView becomeFirstResponder]; // becomeFirstResponder
}
On double tap
-(void)doDoubleTap:(UITapGestureRecognizer*)tapGestureRecognizer
{
UITextView *textView = (UITextView *)tapGestureRecognizer.view;
[self.commentView becomeFirstResponder]; // becomeFirstResponder
// To show the UIMenuController menu
[self setCommentMenuToolTipWithRect:completeRect];
}
NOTE: I am adding [self.commentView becomeFirstResponder]; on every gesture action
UITextView delegate methods
- (void)textViewDidBeginEditing:(UITextView *)inView
{
[self.commentView becomeFirstResponder];
range=[self.commentView selectedRange];
}
USE CASE:
When I double tap to select any word then APP CRASH and UIMenuController does not appear,
but if I add the following line app does not crash
- (void)textViewDidChangeSelection:(UITextView *)textView{
[textView resignFirstResponder];
} // app does not crash
and UIMenuController appears with comment menu items that's great. I was happy that I have fixed the crash issue.
But there is another problem, when I press outside, menu hides and
select any word AGAIN then It does not appear SECOND time.
I have tried all the possible way to show the menu for returns
YES/TRUE to canBecomeFirstResponder. I know, there has to be a view
that claims firstResponder for the menu to show. but how ?
On second time touch, not even calling any gesture recognizer method
From the logs it is clear that when double tap is recognized, same touch update is also sent to another gesture recognizer, which fails.
So, a simple solution would be to avoid detection of other gestures on double tap.
This can simply be achieved by making all other gestures on commentView require doubleTap to fail using requireGestureRecognizerToFail. just add the condition in addGestureToTextView method as shown below.
if (withDoubleTap && self.canScreenPlayEdit) {
[self.commentView removeGestureRecognizer:singleTapGesture];
[self.commentView addGestureRecognizer:doubleTap];
[self.commentView addGestureRecognizer:longPressgesture];
for (UIGestureRecognizer *recognizer in self.commentView.gestureRecognizers) {
[recognizer requireGestureRecognizerToFail:doubleTap];
}
}
This does solve the crash and also shows the menu without calling resignFirstResponder in textViewDidChangeSelection.
However, there seem to be many issues in your code. PLSceneDetailsVC is too complicated and you need to simplify the code. You need to streamline the gesture management or you will end up facing many more such issues.
longPressgesture.minimumPressDuration = 0.2;
My guess the problem is here. 0.2s is way too small to be used for longPress. Probably both were triggered (longPress and double tap).
Change it to higher like 1.5s.

How can a UIViewController inside a UIPageController get touch events?

It seems that the button simply doesn't get pressed.
I tried:
UIPageViewController Gesture recognizers
However, here is the catch
if page transition of UIPageViewController is UIPageViewControllerTransitionStyleScroll then
self.view.gestureRecognizers = self.pageViewController.gestureRecognizers;
NSLog(#"%#",self.pageViewController.gestureRecognizers.description);
NSLog(#"%#",self.pageViewController.gestureRecognizers.description);
will simply be empty.
I tried adding my own gestureRecoqnizers instead of button into the UIViewControllers's subview. It still doesn't work.
So I have a UIPageViewController that display a UIViewController whose view has some button. How do I get that button pressed?
I also tried both solutions here:
http://emlyn.net/posts/stopping-gesture-recognizers-in-their-tracks
None works. First of all I cannot disable gesture recoqnizers of the UIPageViewController because there is no such thing. In scrollView mode, UIPageViewController doesn't have gesture recoqnizers.
I tried to add my own gesture recoqnizers to my own button but those are never called.
I want the UIPageViewController to still handle swipe though. But not tap.
I can't access the gesture recoqnizers of UIPageViewControllers because self.pageViewController.gestureRecognizers is empty. The UIPageViewController seems to just "absorb" all tap events. So my button doesn't get it.
I can add button in front of UIPageViewController but that button will absorb the swiping action which I want UIPageVIewController to still handle.
By the way if we uses the template from IOS (Create a page based app, and change the transition to scroll the pageviewcontroller.gesturerecoqnizers will be empty
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// Configure the page view controller and add it as a child view controller.
self.pageViewController = [[UIPageViewController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStyleScroll navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal options:nil];//Turn this to scroll view
self.pageViewController.delegate = self;
SDDataViewController *startingViewController = [self.modelController viewControllerAtIndex:0 storyboard:self.storyboard];
NSArray *viewControllers = #[startingViewController];
[self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:NULL];
self.pageViewController.dataSource = self.modelController;
[self addChildViewController:self.pageViewController];
[self.view addSubview:self.pageViewController.view];
// Set the page view controller's bounds using an inset rect so that self's view is visible around the edges of the pages.
CGRect pageViewRect = self.view.bounds;
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
pageViewRect = CGRectInset(pageViewRect, 40.0, 40.0);
}
self.pageViewController.view.frame = pageViewRect;
[self.pageViewController didMoveToParentViewController:self];
// Add the page view controller's gesture recognizers to the book view controller's view so that the gestures are started more easily.
self.view.gestureRecognizers = self.pageViewController.gestureRecognizers;
NSLog(#"%#",self.pageViewController.gestureRecognizers.description); //This is empty
while (false);
}
Maybe you can set up a custom gesture recognizer in your UIPageViewController. This way, you will be able to filter through UITouch Events, by implementing this in your gesture recognizer delegate (the one for your UIPageViewController) :
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gesture shouldReceiveTouch:(UITouch *)touch {
if (touch.tapCount > 1) { // It will ignore touch with less than two finger
return YES;
}
return NO;
}
This, you will tell the UIPageViewController gestureRecognizer to not care about touch with one finger. You'll have to try it, but i think it might do the trick. The problem here is you have to ask your user for a two-finger swipe...
Check here for an other answer

UIButton responds to UIPanGestureRecognizer on top of it

I just added a UIView with UIPanGestureRecognizer on top of my view.
The view has several UIButtons which respond to touchUpInside events.
What's weird is that ever since I brought the UIPanGestureRecognizer, when panning, if the UIButton is right underneath the "Panning view", the button would trigger which is not what I am after.
Of course I could make a BOOL flag for "panning", so that the button won't fire, but it seems to me like bad engineering and surely something I am missing. I guess after the first touch, both views intercept the event.
Is it possible to overcome this?
Thanks
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldReceiveTouch:(UITouch *)touch {
if ([touch.view isKindOfClass:[UIButton class]]) {
return NO;
}
return YES;
}
use this method to differentiate GestureRecognizer and Button Acton.
Hope this helps you.
Not sure if this help
[[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePanned:)];
- (void)handlePanned:(UIPanGestureRecognizer*)thePanner{
if (thePanner.state == UIGestureRecognizerStateChanged ){
//disable button
}else if (thePanner.state == UIGestureRecognizerStateEnded) {
//enable button
}else if ( thePanner.state == UIGestureRecognizerStateFailed ){
//enable button
}
}
you can sort-out your problem by following two tactics:-
1)When adding the gesture in your required view, be sure that it will not added with un-required view, in this case is your UIButton, &;
2)At the method/delegate where you handle the case of detecting the gesture, ie what gesture do, before the implementation you assure that this is not the UIButton.
If you insist, I'll try for sample code.
Have a good day ahead. :)

UITableView single clicks

I have a UITableView and UINavigationController, and I'd like to distinguish between two clicks: 1) normal click that selects a row and 2) a click that happens ANYWHERE else on the screen (other than the buttons on the UINavigationController). I wrote this code:
singleTap = [[UITapGestureRecognizer alloc]
initWithTarget:self action:#selector(singleTapHandler:)];
singleTap.numberOfTapsRequired = 1;
The problem with this however is that it overrides the normal clicks that select a row.
I assume you're putting the tap recognizer on either the UIWindow itself, or the window's sole subview. You need to give the tap recognizer a delegate, and that delegate needs to implement gestureRecognizer:shouldReceiveTouch:.
In that method, you want to return NO if the touch is in a button or if the touch is in a table view cell, and YES otherwise. You need to walk up the view hierarchy, starting with the view that the touch landed in, looking for either of those classes.
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
for (UIView *view = touch.view; view; view = view.superview) {
if ([view isKindOfClass:[UIButton class]])
return NO;
if ([view isKindOfClass:[UITableViewCell class]])
return NO;
}
return YES;
}

UIKeyboardTypeNumberPad dismiss keyboard

How is a user suppose to indicate that he/she is done typing if the keyboard is of type UIKeyboardTypeNumberPad? There's no return/done button so how do I know the user wants to dismiss the keyboard?
Thanks
After researching this problem for a while, I found no really satisfactory answers. Some solutions hack a "Done" button, but that doesn't work well for localized applications. My apps don't use any words so I definitely don't want a "Done" button.
As mentioned by taskinoor it is convenient for editing to end if the view is touched elsewhere. That's what I did, and here is code showing the details:
Creating the textField programmatically:
aTextField = [[UITextField alloc] initWithFrame:CGRectMake(0, 0, 50, 50)];
[aTextField addTarget:self action:#selector(c1Update) forControlEvents:UIControlEventEditingDidEnd];
aTextField.keyboardType = UIKeyboardTypeNumberPad;
[yourView addSubview:aTextField];
c1Value = aTextField; // I like to keep a pointer for UI updates, alpha etc.
What happens in c1Update:
- (void)c1Update {
[[self brain] setC1:[c1Value.text intValue]]; // update the model
}
Gesture recognizer on view:
- (void)viewDidLoad {
[super viewDidLoad];
[self updateViewColors];
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(viewTapped)];
tapRecognizer.numberOfTapsRequired = 1;
[self.view addGestureRecognizer:tapRecognizer];
}
- (void) viewTapped {
if (c1Value.isFirstResponder) {[c1Value resignFirstResponder];} // dismisses keyboard, which causes c1Update to invoke.
}
You can either provide a cancel/hide button (outside the keypad) or hide that when touched anywhere outside the keypad. These two are common practices.
try this. it might help you
hide the keypad when touched the screen.
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
}