iOS10 UITextView touch event crash - objective-c

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.

Related

How to stop UIPanGestureRecognizer from recognizing taps

On one of the UIViewControllers of my iPhone app, I have a UIPanGestureRecognizer attached so that when the user swipes to the left or to the right the app advances or goes back one screen. However, the problem is that when the user taps (rather than swipes) on the screen it still advances. How can I stop this from happening. I have pasted the relevant code below:
-(void) addGestureRecognizer
{
UIPanGestureRecognizer *pan;
pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(swipeRecognized:)];
[pan setMinimumNumberOfTouches:1];
[self.view addGestureRecognizer:pan];
}
-(void) swipeRecognized: (UIPanGestureRecognizer *) recognizer
{
if(recognizer.state != UIGestureRecognizerStateBegan) return;
CGPoint velocity = [recognizer velocityInView:self.view];
if(velocity.x > 0)
{
[self.navigationController popViewControllerAnimated:YES];
}
else
{
#try
{
[self performSegueWithIdentifier:NEXT_STEP_SEGUE sender:self];
}
#catch (NSException *exception)
{
//Silently die...muhaha
}
}
}
I would suggest using a UISwipeGestureRecognizer for swipes. Is there a particular reason you're use a pan?
With a UISwipeGestureRecognizer you can specify for which direction it should recognize the gesture.
It's also better for your users, to use the appropriate gestures. That way, they'll feel right at home :)
You should either use a UISwipeGestureRecognizer, as suggested by fguchelaar, or use the translationInView: method to figure out how far the user has actually moved the finger (for taps, that should be near zero).
You also shouldn't return early if the state is not UIGestureRecognizerStateBegan (first line in your method), otherwise your method will only get called once, when the gesture begins, but not during the gesture. If that's what you actually want, a UISwipeGestureRecognizer is all you need. The benefit of the pan gesture recognizer is mostly that you can track the user's finger and provide direct feedback (like moving the view while the finger is still down).

Handling Multiple Gestures on a View

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;
}

MKMapView zoom broken after adding a single tab gesture recognizer

I'm working on an application using a map on which the user have to add an annotation. Very simple. Problem is, right after I added a tap recognizer on my map, the zoom supposed to be triggered after a double tap no longer works. It seems to be a pretty common issue, as I found (and tried, I swear) a lot of potential solutions without any success.
The latest solution I tried (and I felt very clever after finding it by myself) was trying to retrieve the "built-in double tap" recognizer of the MkMapView, so I can reuse it in my "single tap" gesture recognizer. This is what I did :
- (void)viewDidLoad {
[super viewDidLoad];
NSArray *gestures = [map gestureRecognizers];
for (UIGestureRecognizer *r in gestures) {
if ([r class] == [UITapGestureRecognizer class]) {
if (2 == [(UITapGestureRecognizer *)r numberOfTapsRequired]) {
builtInDoubleTap = (UITapGestureRecognizer *)r;
NSLog(#"BOOM! Found it!");
return;
}
}
}
NSLog(#"Guess view did load");
[self enableTapRecognizer];
}
And in my "enableTapRecognizer" method :
- (void)enableTapRecognizer {
UITapGestureRecognizer *mapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handlePosition:)];
if(builtInDoubleTap) {
NSLog(#"require to fail builtInDoubleTap");
[mapGestureRecognizer requireGestureRecognizerToFail: builtInDoubleTap];
}
[map addGestureRecognizer: mapGestureRecognizer];
}
Problem is, it never shows my "Boom! Found it!" debug message. How come it can zoom after a double tap without any gesture recognizer? That doesn't really make sense, does it?
(I also tried using a UIGestureRecognizerDelegate so my gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer method returns YES...)
I'm kinda desperate right now, so if somebody has an idea... maybe something wrong in my map configuration?
I'm targeting iOS5 and running latest xCode.
Thanks !

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{
}

Obj C - resign first responder on touch UIView

I'm trying to get the keyboard to disappear when the screen is touched, a question that is answered all over stackoverflow. I was able to get the keyboard to disappear when the enter key was pressed thanks to a thread here. I'm not having luck on the background touch resigning the first responder. The method is being entered, I have an NSLog in the method saying, "in backgroundTouched" but the keyboard is still there.
I've tried making the UIView a UIControl class so I could use the touch event.
journalComment is a UITextView.
-(IBAction)backgroundTouched:(id)sender
{
[journalComment resignFirstResponder];
NSLog(# "in backgroundTouched");
}
I've also tried having a invisible button under everything that calles the backGroundTouched method. I think it maybe that I'm missing something in interface builder, but I'm not sure what.
Thank you for any help!
This is what works for the done button:
-(BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range
replacementText:(NSString *)text
{
// Any new character added is passed in as the "text" parameter
if ([text isEqualToString:#"\n"]) {
// Be sure to test for equality using the "isEqualToString" message
[textView resignFirstResponder];
// Return FALSE so that the final '\n' character doesn't get added
return FALSE;
}
// For any other character return TRUE so that the text gets added to the view
return TRUE;
}
I found the following code works best with my text view (not text field) without the delegate methods:
first you set up a tap gesture recognizer onto your view :
- (void)viewDidLoad{
UITapGestureRecognizer* tapRecognizer = [[UITapGestureRecognizer alloc]
initWithTarget:self
action:#selector(tap:)];
tapRecognizer.delegate = self;
[self.view addGestureRecognizer:tapRecognizer];
}
and then in your tap method :
- (void)tap:(id)sender
{
// use to make the view or any subview that is the first responder resign (optionally force)
[[self view] endEditing:YES];
}
this should allow your keyboard to be dismissed when you anywhere on the view.
Hope this helps
Try this. We had this problem eariler, but eventually found the right solution.
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
[yourtextfield resignFirstResponder];
// you can have multiple textfields here
}
This should resolve the problem with the keyboard not dissapearing when pushing the background.