Is there a way to get notifications of when the user presses the backspace key on a UITextField, even if the field is empty?
I would like to be able to trigger some code when the user backspaces on an empty field.
[[NSNotificationCenter defaultCenter] addObserver: self selector: #selector(keyPressed:) name: UITextFieldTextDidChangeNotification object: nil];
This is how you get notified on every key the user presses. Then you have to figure out (i think in the notification is a property for it) which key was pressed.
Or you can use the textfield delegate function
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
To figure out what key was pressed
I can think of a technique to get you what you want but its going to take a bit of code. In essence, always keep a single Unicode "space" character at the front of the string. I don't have my book with me, but there is a really thin space character you can use.
You will put most of your code here:
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
when the field begins editing, prefix any string there with the space
when the user appends text as seen in shouldChangeCharactersInRange, append it.
if the user tries to delete the final last character, or a range that contains it, then return NO, make the change, then add back the leading space char.
when the textField is finished editing, remove the leading space
Since you need to detect not when text was deleted but when the backspace key is pressed, you need to do quite a bit more than implement UITextFieldDelegate.
Read this blog post about how UITextField forwards all the UIKeyInput methods to a private class UIFieldEditor. The writer dynamically subclasses UIFieldEditor at runtime in order to detect these events.
Hope this points you in the right direction!
This will detect backspace. Now you can do whatever when backspace is pressed.
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
if (textField==txtMobileNo)
{
const char * _char = [string cStringUsingEncoding:NSUTF8StringEncoding];
int isBackSpace = strcmp(_char, "\b");
if (isBackSpace == -8) {
NSLog(#"isBackSpace");
if (textField.text.length == 9)
{
}
return YES; // is backspace
}
else if (textField.text.length == 10) {
return YES;
}
}
return NO;
}
Related
I've a UITextField with text like, +91- (that's country caller code of India). Now when my user input his number into that textfield, it would look like this, +91-1234567890 that's good, now when he tap (x) delete key from keyboard, I want to restrict deletion, its only possible up to, 1 (first digit of his mobile number), at any case, he should not be able to delete +91-. I'm able to do it with - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string; delegate, like this,
1) First way:
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
if([string isEqualToString:#""]) { //detect back space
if([textField.text hasSuffix:#"-"]) { //has suffix `-`
return NO;
}
}
}
2) Second way:
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
//if text length is length of caller code and detect back space
if(textField.text.length<=4 && [string isEqualToString:#""]) {
return NO;
}
return YES;
}
In both the ways, I'm getting what I want, but not sure its proper or not? Any more smoother way?
why you not try just like the simple method add the one more UIView in prefix of the UItextfield
You can simply show a non-editable UILabel before the UITextField where the user actually enters the number. When you get the input from the user and want to process it, prefix "+91-" before the user input string
I have subclassed UITextField and implemented the UIKeyInput protocol's deleteBackward method to detect backspace being pressed. This works fine on iOS 7 but not on iOS 8.
deleteBackward is not called on the UITextField anymore when I press the backspace key.
I've checked the documentation and the release notes and nothing points to the reason why this could happen. Any pointers?
A lot of people have been saying this is a bug, but being that this problem still exists in the GM I'm starting to think it might be a change in logic. With that said, I wrote this bit of code for my app and have tested it on iOS 7-8.
Add the following method to your UITextField subclass.
- (BOOL)keyboardInputShouldDelete:(UITextField *)textField {
BOOL shouldDelete = YES;
if ([UITextField instancesRespondToSelector:_cmd]) {
BOOL (*keyboardInputShouldDelete)(id, SEL, UITextField *) = (BOOL (*)(id, SEL, UITextField *))[UITextField instanceMethodForSelector:_cmd];
if (keyboardInputShouldDelete) {
shouldDelete = keyboardInputShouldDelete(self, _cmd, textField);
}
}
BOOL isIos8 = ([[[UIDevice currentDevice] systemVersion] intValue] == 8);
BOOL isLessThanIos8_3 = ([[[UIDevice currentDevice] systemVersion] floatValue] < 8.3f);
if (![textField.text length] && isIos8 && isLessThanIos8_3) {
[self deleteBackward];
}
return shouldDelete;
}
This code is slightly before the red line of private API's, however you should have no problem using it. My app with this code is in the app store.
To explain a little, were calling the super implementation of this method to avoid losing code. After were going to call -deleteBackward if there is no text and the iOS version is between 8-8.2.
EDIT: 1/22/15
It also might be helpful to subclass the -deleteBackward method of your subclassed UITextField. This fixes a few conditional bugs. One being if you use a custom keyboard. Heres an example of the method.
- (void)deleteBackward {
BOOL shouldDismiss = [self.text length] == 0;
[super deleteBackward];
if (shouldDismiss) {
if ([self.delegate respondsToSelector:#selector(textField:shouldChangeCharactersInRange:replacementString:)]) {
[self.delegate textField:self shouldChangeCharactersInRange:NSMakeRange(0, 0) replacementString:#""];
}
}
}
EDIT: 4/13/15
As #Gee.E commented, iOS 8.3 has fixed this issue. The code has been updated to reflect the changes.
You can detect when user deletes text by using backspace by implementing UITextField delegate method:
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
if (range.length==1 && string.length==0)
NSLog(#"backspace tapped");
return YES;
}
You must look an example for MBContactPicker on github. Deletion of contacts at MBContactPicker via Backspace button on iOS8 tested by me. And it works greatly! You can use its as example.
Author of MBContactPicker use next method: When UITextField must become empty (or before call becomeFirstResponder when it is empty), he save single whitespace symbol there. And then when you press Backspace button (when focus was set to end of text of your UITextField), method
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
will work. Inside it you must use check like this:
NSString *resultString = [textField.text stringByReplacingCharactersInRange:range withString:string];
BOOL isPressedBackspaceAfterSingleSpaceSymbol = [string isEqualToString:#""] && [resultString isEqualToString:#""] && range.location == 0 && range.length == 1;
if (isPressedBackspaceAfterSingleSpaceSymbol) {
// your actions for deleteBackward actions
}
So, you must always control that UITextField contains single whitespace.
This is not hack. So, user willn't noticed about some behaviour was changed
Swift 2.2:
func textView(textView: UITextView, shouldChangeTextInRange range: NSRange, replacementText text: String) -> Bool {
if text == "" {
print("Backspace has been pressed")
}
return true
}
In iOS8, some custom keyboards delete whole word, so only check string.length is OK.
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
if (string.length==0) { //Delete any cases
if(range.length > 1){
//Delete whole word
}
else if(range.length == 1){
//Delete single letter
}
else if(range.length == 0){
//Tap delete key when textField empty
}
}
return YES;
}
This does not explicitly answer the original question but worth nothing that in the documentation for textField(_:shouldChangeCharactersIn:replacementString:), it says:
"string: The replacement string for the specified range. During typing, this parameter normally contains only the single new character that was typed, but it may contain more characters if the user is pasting text. When the user deletes one or more characters, the replacement string is empty."
Thus, we can detect backspaces in a UITextFieldDelegate if we implement textField(_:shouldChangeCharactersIn:replacementString:) and check if the length of string is 0.
A lot of other answers here have used this same logic without referencing the documentation so hopefully getting it right from the source makes people more comfortable using it.
Swift 2.0 version for Detecting BackSpace based deletion, referencing code post from almas
//For Detecting Backspace based Deletion of Entire Word in TextField
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
if (range.length == 1 && string.isEmpty){
print("Used Backspace")
}
return true
}
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
const char * _char = [string cStringUsingEncoding:NSUTF8StringEncoding];
int isBackSpace = strcmp(_char, "\b");
if (isBackSpace == -8) {
NSLog(#"Backspace was pressed");
}
return YES;
}
Basically this method detects which button you are pressing (or have just pressed). This input comes in as an NSString. We convert this NSString to a C char type and then compare it to the traditional backspace character (\b). Then if this strcmp is equal to -8, we can detect it as a backspace.
swift 2:
if (string.characters.count == 0 && range.length == 1) {
return true
}
you should use like this string.characters.count
func keyboardInputShouldDelete(_ textField: UITextField) -> Bool {
}
This function is called when you hit delete key
I have looked all over for an answer to this but essentially what I am trying to do is when a person pressed the colon key on their iphones keyboard I want to be notified and perform a certain action. I hope this makes sense. If you do offer an answer keep in mind I am a relatively new IOS developer :)
Thanks!
edit: Incase my above statement didn't quite make sense this is what will happen ideally:
user taps on textfield
user presses the number 1 key
notification is sent that user pressed the number 1 key
instead of the number 1 printed, the text will be replaced with the number 2.
this is a simple example.
Here's an example of a delegate method for a UITextField where if the user tries to enter an uppercase character it will appear as a lowercase character instead:
-(BOOL)textField:(UITextField *)textField
shouldChangeCharactersInRange:(NSRange)range
replacementString:(NSString *)string {
NSString* lc = [string lowercaseString];
if ([string isEqualToString:lc])
return YES;
textField.text =
[textField.text stringByReplacingCharactersInRange:range
withString:lc];
return NO;
}
You should be able to do something similar for your particular use case.
As mentioned before, use this callback and change in there:
-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
//check here if the new character is the one you are looking for
if ([string isEqualToString:#"a"])
{
//create a new string with the character you want to use instead
NSString *newText = [textField.text stringByReplacingCharactersInRange:range withString:#"A"];
//set it as the text for your text field
[textField setText:newText];
return NO;
}
return YES;
}
I am working on an application in iOS that displays a random string to the user, and asks the user to enter that same string through the keypad. What I would like to do is compare the string the user has entered, ONLY after they have pressed the 'return' key. Is this possible?
Here is my relevant method that I am doing this in:
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
if (textField.tag == 1)
{
userString = #"";
userString = keypadText.text;
[keypadText resignFirstResponder];
//here 'rand' is the random string that was presented earlier to the user, and userString is the text that the user has entered.
if ([userString isEqualToString:rand]) {
//do something
}
else {
//do something else
}
[self.navigationController popViewControllerAnimated:YES];
}
return YES;
}
As I said, I ONLY want to do the comparison after the user has pressed the 'return' key, and up until then, allow the user as much opportunity to enter the given text, and make whatever corrections necessary.
Can someone show me how to do this?
Just use textFieldDidFinishingEditing instead of shouldChangeCharactersinRange.
Of course, there is also
- (BOOL)textFieldShouldReturn:(UITextField *)textField
The latter is the better way.
So simple.
short version: How can I make a UITextField box remove all content on the users first keypress? I don't want the info removed until the user starts typing something. ie, clearing it on begin edit is not good enough.
long version: I have three UITextField that loop around (using the return key and catching the press in the "shouldReturn" method. There is text already in the UITextField, and if the user doesn't type anything and just goes to the next UITextField, the value should stay (default behaviour).
But I want it that if the user starts typing, it automatically clears the text first. Something like having the whole field highlighted, and then typing anything deletes the fiels and then adds the user keypress.
"Clear when editing begins" is no good, because the text is immediately cleared on the cursor appearing in the field. That's not desired. I thought I could use the placeholder here, but that doesn't work as a default, and I can't find a default value property. The Highlighted and Selected properties don't do anything in this regard either.
There is a delegate method called
textFieldDidBeginEditing:(UITextField*) tf{
tf.startedEdinting = YES;
}
textFeildDidEndEditing: (UITextField*) tf {
tf.startedEditing = NO;
}
Add startEditing in a category to UITextField.
Then if value changes clear the field:
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
if (textField.startEditing){
textField.text = string;
} else {
textField.text = [textField.text stringByReplacingCharactersInRange:range withString:string];
}
}
You can add the property to the UITextField category in the following way:
.h
#property (nonatomic, assign) BOOL startEditing;
.m
#dynamic startEditing;
- (void) setStartEditing:(BOOL)startEditing_in{
NSNumber* num = [NSNumber numberWithBool:startEditing_in];
objc_setAssociatedObject(self, myConstant, num, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (BOOL) startEditing{
NSNumber* num = objc_getAssociatedObject(self, myConstant);
return [num boolValue];
}
Declare a BOOL variable in your .h file like.
BOOL clearField;
And implement the delegate methods like:
-(void)textFieldDidBeginEditing:(UITextField *)textField
{
clearField = YES;
}
-(void)textFieldDidEndEditing:(UITextField *)textField
{
clearField = NO;
}
-(BOOL)textFieldShouldReturn:(UITextField *)textField
{
clearField = NO;
}
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
if(clearField)
{
textField.text = #""
clearField = NO;
}
}
I want to thank people for their answers, I implemented both of the main methods described here and both worked flawlessly. But I have since come across a much simpler, nicer answer and involves only one line of code :)
In the textField's didBeginEditing method, place [self.textField selectAll:self]; or [self.textField selectAll:nil];
The original answer I found had selectAll:self but this shows the cut/copy/paste menu. If you send nil instead of self the menu doesn't appear.
Adding this one line of code highlights the text on entering the textField (so gives the user a visual cue), and only removes everything once a key is pressed.
Another solution that fulfils the same purpose is by simply using a text field placeholder which is defined as:
The string that is displayed when there is no other text in the text field.
So as soon as the user starts typing, the placeholder text disappears.
That's something you can set from the storyboard, or programmatically. (Yes it took me two hours trying to figure it the harder way.. when the solution was literally one line change of code).
If you want to clear the text one the user interacts with it, there is an option in interface builder to where you can set the text field to "Clear when editing begins."
Try to use the following method.
- (BOOL) textField: (UITextField *)theTextField shouldChangeCharactersInRange: (NSRange)range replacementString: (NSString *)string {
if(isFirsttime==YES)
{
textfield.text==#"";
isFirsttime=NO;
}
return YES;
}
Declare and initialize a NSString variable for your textField's initial text
NSString *initialText=#"initial text";
Then implement methods:
-(void)textFieldDidBeginEditing:(UITextField *)textField
{
if(textField.text isEqualToString:initialText)
{
textField.text=#"";
}
}
-(void)textFieldDidEndEditing:(UITextField *)textField
{
if(textField.text isEqualToString:#"")
{
textField.text=initialText;
}
}