UITextView double tap to edit - ios

So I have a UITextView I'm using to make a moveable, editable label (My prior searches on SO showed this to apparently be the best option). I need to have the user double tap to enable editing and set it to become the first responder. I can't find a way to do this, and all my other searches have turned up either outdated answers or vague answers. Nothing I've tried seems to work. I've tried using the UITextViewDelegate to have it start editing as opposed to selecting text using textViewDidChangeSelection:, but it doesn't work until you change the current selection. I also tried using a custom UITapGestureRecognizer like so:
UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(doubleTap:)];
[doubleTap setNumberOfTapsRequired:2];
[doubleTap setNumberOfTouchesRequired:1];
[newLabel addGestureRecognizer:doubleTap];
-(void)doubleTap:(UITapGestureRecognizer *)sender
{
NSLog(#"Double tap detected");
NSLog(#"Sender view of class %#", [[sender view] class]);
UITextView *tappedView = (UITextView *)[sender view];
[tappedView setEditable:YES];
[tappedView becomeFirstResponder];
// [tappedView setEditable:NO];
}
The double tap gesture is never called. I'm not sure why. Strangely, it also doesn't select text either while it's like that. It seems to just break double tap gestures. Is there a way to get rid of the standard double tap selection gesture, or to modify it? Should I subclass UITextView and, if so, what would I change?

I found a neat solution, with less code (Swift 4.2):
Create a Custom UITextView Class and in it write this
override var canBecomeFirstResponder: Bool {
if self.isEditable {
return true
} else {
self.isEditable = true
return false
}
Then in viewDidLoad() write the following (or deselect 'Editable' under behaviour in the storyboard editor)
myTextView.isEditable = false
Then simply put the same code somewhere when you're finished editing the text - eg. textViewDidEndEditing(_ textView: UITextView)
myTextView.isEditable = false
When the view loads, the textView is not editable and all data detectors work. A second tap then allows it to be edited (and disables the data detectors).
As soon as you exit the textView, it's set to not editable again and the data detectors work.
All this without any gesture recognisers! :-D

Sublassing UITextView, I added this method to the .m file.
-(BOOL)canBecomeFirstResponder
{
if (self.editable == YES){
return YES;
}
else{
return NO;
}
}
In addition to this, I used
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGesture
{
return YES;
}
This is the best way I found to solve my problem. I only wanted to allow double tapping to edit. I wanted no text selection, scrolling, etc to happen until it was double tapped. To futher finish this, you'll need a to use a UITextViewDelegate to turn textView.editable = NO
-(void)textViewDidEndEditing:(UITextView *)textView
{
[textView setEditable:NO];
}

You should set the target of the Gesture Recognizer as the textfield.
UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:textField action:#selector(doubleTap:)];

Related

UITextView clear selection when touching outside

There is UITextView which is selectable but not editable.
When I highlight text and touch outside the selection is not cleared.
I tried to use UIMenuControllerWillHideMenu, it helps to clear selection when I touch other as the menu controller dismiss. But the problem is when I change the selection the selection is cleared because the menu controller dismissed and re appear after the selection modification.
Has anyone workaround this problem too?
Add UITapGestureRecognizer and call endEditing:YES on your UITextView.
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(onTextViewTap:)];
- (void)onTextViewTap:(UITapGestureRecognizer *)tapGesture {
if (tapGesture.state == UIGestureRecognizerStateEnded) {
self.textView.selectedTextRange = nil;
}
}

UITextView can't dismiss the keyboard in programmatically-created UITableView

There seems to be lots on this subject. But I couldn't get any of the solutions to work.
My code creates a tableView full of cells with varying contents (based on a JSON), the user should enter the info in each cell. The problem I am having is, when the user taps somewhere outside the cell (i.e. on the tableView background) I want the keyboard to dismiss.
didSelectRowAtIndexPath: method is not good.
touchesBegan: does not fire at all (tableView has user interaction enabled, but I assume there is some other reason).
I have added a gesture to the tableView, but then I cannot start editing.
I know the resignFirstResponder. But I don't know which field is being edited. so I think, I need to go with the endEditing: method. But I just couldn't get it called, when user touches outside of a cell.
Any help would appreciated.
If that is a textField as #JFS said, you can set the delegate and resign it in this below method.
- (BOOL)textFieldShouldReturn:(UITextField *)textField
Else If that is a textView, You can add a toolbar with done button as inputAccessoryView to your textView and resign your textView.
I am having two more ideas
Bad Idea:
Try to make an invisible button in the cell background and add an action method to resign it. But this will get confused, when you are using didSelectRowAtIndexPath: method of your UITableView.
Good Idea:(Good one)
UITableView surely will have UIScrollview. So in the scrollViewDidScroll: method, set [self endEditing:YES] will surely work.
Good Idea 2
Create a new View or Button and place it on top of the all views, when a textView is in editing and call the endEditing: method when user touches the view or button.
TO GET A TOUCH DETECTION IN TABLEVIEW:
Some of these Q&A will help you.
https://stackoverflow.com/a/8787019/1083859
Why does my UITableView not respond to touchesBegan?
https://stackoverflow.com/a/8786706/1083859
Okay after lots of playing around a managed to figure (something) out, it is not great but works.
Most of the gaps around the table is taken up by section headers (a small amount by the cell walls). To get these areas to call a method I added a gesture to the section header like this;
-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
NSString *sectionTitle = [self tableView:tableView titleForHeaderInSection:section];
if (sectionTitle == nil)
{
return nil;
}
UILabel *label = [[UILabel alloc] init];
label.frame = CGRectMake(20, 8, 320, 20);
label.backgroundColor = [UIColor clearColor];
label.textColor = [UIColor colorWithRed:76/255.0 green:86/255.0 blue:108/255.0 alpha:255/255.0];
label.shadowColor = [UIColor grayColor];
label.shadowOffset = CGSizeMake(-0.0, 0.0);
label.font = [UIFont boldSystemFontOfSize:16];
label.text = sectionTitle;
UIView *view = [[UIView alloc] init];
[view addSubview:label];
UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(eventMenuTap)];
[view addGestureRecognizer:singleTap];
return view;
}
This also enabled me to customize the label for each section. The net reseult is now I have the keyboard closing when the table is scrolled and if (nearly) anywhere is clicked outside of a cell (i.e. a header) then the eventMenuTap is called.
-(void)eventMenuTap
{
NSLog(#"Tap has begun...");
[self.view endEditing:YES];
}
Thanks for all the ideas and help with this.
For any passersby, the simplest and most effective solution I have found requires 2 things:
1: Subclass your table view and add the following to its .m:
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
// send your own dismiss message; you can use self.delegate to refer to TV's TVC...
[((SearchVC *)((UITableViewController *)self.delegate).parentViewController).searchBar resignFirstResponder];
// crucial!
[super touchesBegan:touches withEvent:event];
}
2: Add the following to your TVC:
- (void) scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
// send your own dismiss message...
[((SearchVC *)self.parentViewController).searchBar resignFirstResponder];
}
This works beautifully for me. I initially tried adding touchesBegan to my subclassed cell, but found that was insufficient and, after adding these two things, superfluous.

how to add double tap gesture to UITextView

currently I want to let UITextView to have a double tap gesture. It seems UITableView has its own double tap gesture, when we double tapped, some text will be selected. So I want to remove this default double tap gesture to my own gesture recognizer. I tried many methods, and all failed. It seems there's no way to remove the default recognizer of UITextView. I also want to add a transparent view on this UITextView to do double tap event, but this subview will block other gestures on UITextView. Is there some method to add double tap gesture recognizer to UITextView? I really hope there will be a work around.
I am still expecting a work around of iOS5 :)
There are a number of other gesture recognizers attached to a text view. Since you don't seem to need them. You can remove them.
myTextView.gestureRecognizers = nil;
before adding your double tap recognizer. It works.
then you can add
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(mySelector)];
tapRecognizer.numberOfTapsRequired = 2;
tapRecognizer.numberOfTouchesRequired = 1;
[myTextView addGestureRecognizer:tapRecognizer];
I have the solution on iOS6, we can use UIGestureRecognizerDelegate, and override gestureRecognizerShouldBegin: and gestureRecognizer:shouldReceiveTouch:. In this two method, we can check if the gesture is doubleTapGestureForZooming, if not return NO, or return YES. This works perfect in iOS6, but in iOS5 these two delegate method hasn't been called, so iOS5 may need another workaround.
Finally, I get the workaround, we can override the addGestureRecognizer method of UITextView to remove default gesture, wish this will help somebody else.
PS: we really can't remove system gestures of UITextView, we even can't change their property. It seems when event happens, all gestures of UItextview will be added again.
I know this question is old, but to keep it current for future searchers, I figured I would add another solution that has worked for me from iOS 7 through 10. It basically brings together the solutions discussed here and here but tweaks them to get the UITextView to recognize the custom double tap.
It does this by subclassing the UITextView and overriding the addGestureRecognizer: method in order to inject our custom callback into the double-tap gesture and configure the single-tap gesture to respect the new double tap hook.
I do this in addGestureRecognizer: because a UITextView constantly deletes and adds gestures depending on its current state and so you constantly have to reset it back up.
This code should be enough to get someone started:
#interface MyCustomTextView ()
/**
* we want to keep track of the current single-tap gesture so we can make sure
* it waits for a double-tap gesture to fail before firing
*/
#property (weak, nonatomic) UITapGestureRecognizer *singleTap;
/**
* we want to keep track of the current double-tap gesture so we can tell a single
* tap gesture to ignore this double-tap when the single tap gesture changes
*/
#property (weak, nonatomic) UITapGestureRecognizer *doubleTap;
#end
#implementation MyCustomTextView
/**
* this will fire when the text view is double-tapped
*
* #param tgr
*/
- (void)_handleTwoTaps:(UITapGestureRecognizer *)tgr
{
// ADD CODE HERE
}
/**
* the reason why I've overridden this methods is these gestures change quite a bit
* depending on the state of the UITextView, (eg, when in focus and out of focus)
* and so this provides a reliable way to make sure any new gestures get updated
* with custom overrides.
*
* #param gestureRecognizer
*/
- (void)addGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
{
[super addGestureRecognizer:gestureRecognizer];
if ([gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]]) {
UITapGestureRecognizer *tgr = (UITapGestureRecognizer *)gestureRecognizer;
if ([tgr numberOfTapsRequired] == 1 && [tgr numberOfTouchesRequired] == 1) {
self.singleTap = tgr;
if (self.doubleTap) {
[tgr requireGestureRecognizerToFail:self.doubleTap];
}
} else if ([tgr numberOfTapsRequired] == 2 && [tgr numberOfTouchesRequired] == 1) {
[tgr addTarget:self action:#selector(_handleTwoTaps:)];
self.doubleTap = tgr;
if (self.singleTap) {
[self.singleTap requireGestureRecognizerToFail:tgr];
}
}
}
}
// NOTE: I'm not sure if this is needed but it's been there for years
// and so I thought I would include it just in case
- (void)removeGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
{
if ([gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]]) {
UITapGestureRecognizer *tgr = (UITapGestureRecognizer *)gestureRecognizer;
if ([tgr numberOfTapsRequired] == 2 && [tgr numberOfTouchesRequired] == 1) {
[tgr removeTarget:self action:#selector(_handleTwoTaps:)];
}
}
[super removeGestureRecognizer:gestureRecognizer];
}
#end

GestureRecognizer Interferes w/ MapKit Popup

I have a simple MapKit app working fine in iOS. It has annotation and when the user clicks on them, the little gray default popup is displayed with the title / subtitle. I even added a UIButton view into it.
So the problem is, I have a search bar above my map. I wanted to resignFirstResponder from the search box whenever the user clicks on the MapView, so I added a simple tap gesture responder. Worked great except now the little gray detail popups no longer show up (only the annotation pins)! I can still tap, zoom, move around etc. Just no popups.
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapped:)];
tap.cancelsTouchesInView = NO;
tap.delaysTouchesBegan = NO;
tap.delaysTouchesEnded = NO;
[mapView addGestureRecognizer:tap];
-(IBAction)tapped:(UITapGestureRecognizer *)geture {
[searchBar resignFirstResponder];
}
Is it possible to have the best of both worlds?
I used a delegate method similar to the following to arbitrate between touches that should go to my custom view's pan gesture recognizer and touches that should go to the scroll view that contained my custom view. Something like it might work for you.
// the following UIGestureRecognizerDelegate method returns YES by default.
// we modify it so that the tap gesture recognizer only returns YES if
// the search bar is first responder; otherwise it returns NO.
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
if ((gestureRecognizer == self.tapGestureRecognizer) &&
(gestureRecognizer.view == self.mapView) &&
[searchBar isFirstResponder])
{
return YES; // return YES so that the tapGestureRecognizer can deal with the tap and resign first responder
}
else
{
return NO; // return NO so that the touch is sent up the responder chain for the map view to deal with it
}
}

UITextfield's clear button hides keyboard when its inside UIScrollView

I have a textfield inside a UIScrollView and i want to show a clear button when user starts editing. Also i need to hide keyboard when user taps the background of UIScrollview (but not the textfield). Displaying that clear button isn't a problem, the problem is that when clear button is tapped keyboard gets hidden and the text field doesn't get cleared. Obviously the problem is with the gesture recognizer, because method dealing with this gets fired when the clear button is clicked (but it's not fired when the text field is tapped). Here's my code :
//adding gesture recognizer so i can hide keyboard when user taps scrollview
- (void) textFieldDidBeginEditing:(UITextField *)textField
{
if (self.tapOutside == nil) self.tapOutside = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(textFieldTouchOutSide:)];
[self.scrollView addGestureRecognizer:self.tapOutside];
}
//This hides keyboard BUT IS ALSO CALLED WHEN CLEAR BUTTON IS TAPPED
- (void)textFieldTouchOutSide:(id)sender
{
[self.textfield resignFirstResponder];
}
//NEVER GETS CALLED
- (BOOL) textFieldShouldClear:(UITextField *)textField {
return YES;
}
Any ideas how to solve this? Maybe better way to add gesture recognizer? I can't think of no elegant solution ... Thanks a lot in advance...
I had the same problem and solved it implementing the following method:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
// Disallow recognition of gestures in unwanted elements
if ([touch.view isMemberOfClass:[UIButton class]]) { // The "clear text" icon is a UIButton
return NO;
}
return YES;
}
Don't forget to conform to the "UIGestureRecognizerDelegate" protocol and set the delegate (using your vars):
self.tapOutside.delegate = self;
Cheers
I was just having this issue and this solution worked, however if you do have other buttons on the view that you allow the user to tap while filling out the form you can do the following:
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
// Disallow recognition of gestures in unwanted elements
if ([touch.view isMemberOfClass:[UIButton class]] && [touch.view.superview isMemberOfClass:[UITextField class]]) {
// The "clear text" icon is a UIButton
return NO;
}
return YES;
}
This will narrow down the case to only return No if the button is a subview of a UITextField, as is the case with the clear button, but still hide the keyboard if they touch a normal button that would normally execute your gesture code.

Resources