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
Related
While trying to allow multi token deletions, as user holds the backspace key in VENTokenField to act the same as the native email app, or messages app, I have come across many problems...
First, I can only detect one tap on the backspace key from the initial code the VENToken's UITextField subclass offer (which is technically touching private API) - (BOOL)keyboardInputShouldDelete:(UITextField *)textField. That is fine, but not helpful for detecting long press on backspace button, which only works while you actually have characters in a certain UITextField, and not while the UITextField is empty such as in our case.
I have also came across this blogpost which suggest another approach of accessing more of the private API, however, does not offer solution to my problem. As it's not documented, I was wondering if there is a valid way to detect this event at all?
I've resolved it by first, comment out anything that was in VENBackspaceTextField class'
keyboardInputShouldDelete:(UITextField *)textField
Then, added 2 consts in VENTokenField header:
NSString * const kTextEmpty = #"\u200B"; // Zero-Width Space
NSString * const kTextHidden = #"\u200D"; // Zero-Width Joiner
Everytime the token becomes first responder, make sure the textField has the empty text:
- (void)inputTextFieldBecomeFirstResponder {
[self.inputTextField becomeFirstResponder];
if (self.tokens.count) {
[self.inputTextField setText:kTextEmpty];
}
...
}
And set it to hidden when cursor is not visible:
- (void)setCursorVisibility {
NSArray *highlightedTokens = [self.tokens filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(VENToken *evaluatedObject, NSDictionary *bindings) {
return evaluatedObject.highlighted;
}]];
BOOL visible = [highlightedTokens count] == 0;
if (visible) {
[self inputTextFieldBecomeFirstResponder];
} else {
[self.invisibleTextField becomeFirstResponder];
[self.invisibleTextField setText:kTextHidden];
}
}
Then, modified the textField Delegate method:
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
if (self.tokens.count && [string isEqualToString:#""] && [textField.text isEqualToString:kTextEmpty]){
VENToken *lastToken = [self.tokens lastObject];
lastToken.highlighted = YES;
[_inputTextField setText:kTextHidden];
_inputTextField.alpha = 0.0;
return NO;
}
if ([textField.text isEqualToString:kTextHidden]){
[self deleteHighlighted];
[self unhighlightAllTokens];
return (![string isEqualToString:#""]);
}
//If there are any highlighted tokens, delete
[self deleteHighlighted];
return YES;
}
I am making a simple application that has a TextField and a TextView
Whatever we write in textfield will get updated in textview (Both are in a single View, and it is the Root view).
the textfield will get updated every time i hit any key in the keyboard.
I know that the delegate method "textfielddidEditing" helps , but it only helps when we click on the field or click back. I want the method that invokes every time when i hit anything in the keyboard.
Any help is appreciated.
I'm still learning iOS
Please use below textfield delegate method. Its easy and simple to use with all appen and delete mechanism. You can delete multiple characters too by selection. Just store newString value to your textView
-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
NSMutableString *newString = [NSMutableString stringWithString:textField.text];
if (range.length > 0) {
[newString replaceCharactersInRange:range withString:string];
}
else {
[newString appendString:string];
}
NSLog(#"%#",newString);
return YES;
}
You can use UITextField Delegate method. It invokes every time when you hit anything in the keyboard. Write your code as per your requirement..
-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
if (textField.text.length>0)
{
self.yourtextview.text=textField.text;
}
return YES;
}
Hope it helps you..
The above code mentioned by Vidhyanad900 will work. But its if would fail in case a user deletes the text. (Hits backspace or selects complete text and deletes it)
Hence Modifying the code
-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
int iNewLength = [textField length];
if([text isEqualToString:#""]) //If text is deleted
{
if([textField selectedTextRange]) //If Text is Seleceted And Deleted
iNewLength = [textField length] - [textField selectedRange].length;
else
iNewLength = [textField length] - 1;
}
if (iNewLength > 0)
{
self.yourtextview.text=textField.text;
}
return YES;
}
i have a little problem.
There are two textFields in my TableView which set the property of an object. In order to do so i want to force the user to write something in the textField before the string is actually been set to the object. So basically a simple ([textField.text length] > 0) thing.
But i want that the user have to write strings in both the two textFields to finally enable the "Done"-Button.
I solved this earlier but with only one text Field with the following UITextFieldDelegate method.
- (BOOL)textField:(UITextField *)theTextField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
NSString *newText = [theTextField.text stringByReplacingCharactersInRange:range withString:string];
self.doneBarButton.enabled = ([newText length] > 0);
return YES;
}
My solution for the new problem, so now with two textFields is this one:
- (BOOL)textField:(UITextField *)theTextField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
NSString *newText = [theTextField.text stringByReplacingCharactersInRange:range withString:string];
if ([theTextField.placeholder isEqualToString:#"textField1"]) {
if ([theTextField.text length] > 0) {
enabledVokabel = YES;
} else {
enabledVokabel = NO;
}
}
if ([theTextField.placeholder isEqualToString:#"textField2"]) {
if ([theTextField.text length] > 0) {
enabledUebersetung = YES;
} else {
enabledUebersetung = NO;
}
}
self.doneBarButton.enabled = (enabledVokabel && enabledUebersetung);
return YES;
}
So i want the doneBarButton been enabled when both of the textFields (textField1 and textField2) are filled with text. But i want it that way that if the user has deleted the text he/she just wrote in the doneBarButton is disabled as soon as the textFields are empty.
It doesn't work that way. Do you have a solution? Or maybe a better way to solve it?
Either just connect value changed in interfacebuilder to a IBAction method in any of the classes you have in your view. Or you can do it in code with:
[textField addTarget:self
action:#selector(myIBActionMethod:)
forControlEvents:UIControlEventEditingChanged];
And check the length of the input.
You can of hook up both textfields to the same method and check the length of both textfields every time its called if you have IBOutlets to them both.
I'd keep a reference for both UITextViews, lets say.-
IBOutlet UITextView *textView1;
IBOutlet UITextView *textView2;
properly linked to your xib/storyboards. Also, I'd rather use
- (void)textViewDidChange:(UITextView *)textView
callback. According to shouldChangeCharactersInRange docs, it looks like this method is called before actually changing the text.
As for the enabled condition, it would look something like this.-
self.doneBarButton.enabled = [textView1.text length] > 0 && [textView2.text length] > 0;
I have a text field where I want to insert a phone number. The problem is that I use number pad and there is not any enter key and I without that key I don't know how to close the text field.
Is there any way of checking how many number os characters there are in a text field and close the number pad when there are X chars?
Sorry for my english and sorry for the noob question but I am just starting with ObjC.
Thanks *
Sorry guys, I forgot to tell you that I already have tried this:
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
if (textField.text.length== 9) {
[textField resignFirstResponder];
}
return YES;
}
Yes, you have two choices in my opinion, you can:
A. Check the number of chars returned in delegate method shouldChangeTextInRange and then if your limit is reached resign first responder (when you resign first responder the keyboard dismisses,
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
if ( textView.text.length + (text.length - range.length) == 10) //or whatever value you like
{
[textField resignFirstResponder];
}
}
Or B:
Override the touchesEnded method of your view and call resignFirstResponder, so that when the user touches the view outside the textField, the keyboard dismisses. - this is what I do in my app.
Like this:
Just add this method to your viewController, it will be called automatically when the touch in the view ends, in this method you simply resignFirstResponder and the keyboard will disappear.
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[textField resignFirstResponder];
}
I think it's better to do this in touchesEnded than began, it 'feels' nicer to have the keyboard dismiss when you lift your finger from the view, rather than when to initially touch it.
I found that resignFirstResponder do not include the last char typed. I had to do it in this way,
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
DLog(#"Existing: %#, string: %#, range:(%lu, %lu)", textField.text, string, (unsigned long)range.location, (unsigned long)range.length);
if (textField == field2.textField) {
if ( textField.text.length + (string.length - range.length) == kMyNumberLength) // e.g. 10
{
textField.text = [textField.text stringByReplacingCharactersInRange:range withString:string];
[textField resignFirstResponder];
}
if (textField.text.length + string.length - range.length > kMyNumberLength) {
return NO;
} else {
return YES;
}
}
return YES;
}
Swift
when reaching the maximum number of characters, add the new char to the current text in the textfield and then dismiss the keyboard
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let maxLength = 5
let currentString: NSString = textField.text! as NSString
let newString: NSString = currentString.replacingCharacters(in: range, with: string) as NSString
if newString.length == maxLength {
textField.text = textField.text! + string
textField.resignFirstResponder()
}
return newString.length <= maxLength
}
Every time a user adds a number, the delegate of your text field will be notified through this method:
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
NSUInteger length = [[textField text] length] - range.length + string.length;
if (length == ...) ...
}
You can set up your delegate in such a way as to watch the length of the modified content of your text field, and close the field when the expected length is reached.
However, this has a high potential to frustrate your users a lot: they would make a mistake entering the last character every now and then, and the field is going to close on them, not letting them correct the problem. A better approach is to dismiss the pad when users tap away from your text field, which keeps end-users in control of what is going on:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[textField resignFirstResponder];
}
You can just put a toolbar with a Done on it, on the click on that button you can hide keyboard and toolbar as well.
-(IBAction)doneButtonClicked:(id)sender{
[yourTextField resignFirstResponder];
[toolBar setHidden:YES];
}
or you can use this custom tool
Had issues with some of the suggestions above not retaining the last character entered.
I found this worked well for me.
Method simply examines the length of the text as it is building up and then runs [textField resignFirstResponder] to dismiss.
All usual textField delegates will run for the textField at point field loses focus and keyboard is dismissed.
- (void)textFieldDidChangeSelection:(UITextField *)textField {
NSLog(#"textField.text: %#", textField.text);
if (textField.text.length == 4) // If 4 is your max-length
[textField resignFirstResponder];
}
Use this textfield delegate method to find out the length of textField.text
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
int textLength = [textField.text length] + 1;
return YES;
}
I am writing validation for my textfield, I found something interesting that whether I can check how many digits I am typing into the textfield at real time. My text field input must be 8 digit number. So I want to change the text inside the text field to green colour when I reach 8 digit and change colour when it's not.
How can I do that? Please help me, thanks in advance.
Using -textField:shouldChangeCharactersInRange:replacementString: is probably a bad solution because it fires before the text field updates. This method should probably be used when you want to change the text of the text field before the keyboard automatically updates it. Here, you probably want to simply use target-action pairing when editing value changes:
[textField addTarget:self action:#selector(checkTextField:) forControlEvents:UIControlEventEditingChanged];
Then, in - (void)checkTextField:(id)sender, try this:
UITextField *textField = (UITextField *)sender;
if ([textField.text length] == 8) {
textField.textColor = [UIColor greenColor]; // No cargo-culting please, this color is very ugly...
} else {
textField.textColor = [UIColor blackColor];
/* Must be done in case the user deletes a key after adding 8 digits,
or adds a ninth digit */
}
-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
NSString * searchStr = [textField.text stringByReplacingCharactersInRange:range withString:string];
// [textField2 setText:[textField1.text stringByReplacingCharactersInRange:range withString:string]];
NSLog(#"%#",searchStr);
return YES;
}
Use UITextFieldDelegate. especially this function
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
and you can get sample codes links from here..
[textField addTarget:self action:#selector(checkTextField) forControlEvents:UIControlEventEditingChanged];
Try it!
Set up a delegate for the UITextField and implement the method – textField:shouldChangeCharactersInRange:replacementString: Details and examples are in the UITextFieldDelegate Protocol Reference, but here's a quick example:
- (BOOL)textField:(UITextField *)textField
shouldChangeCharactersInRange:(NSRange)range
replacementString:(NSString *)string
{
NSString *text = [textField text];
// NOT BACKSPACE
if ([string length] || text.length + string.length < 8) {
return YES;
} else if (text.length + string.length > 8) {
return NO;
} else {
// DO SOMETHING FOR LENGTH == 8
return YES;
}
}
Update Swift 4.0
txtFieldAdd.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)
Now call your function. In my case, this will enable the save button
#objc func textFieldDidChange(_ textField: UITextField) {
if (txtFieldAdd.text?.isEmpty)! {
btnSave.isEnabled = false
}
else {
btnSave.isEnabled = true
}
}
Hope it helps.
This is how you get realtime validation without setting up anything other than the delegate of the textfield:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
// Creating the future string. The incoming string can be a single character,
// empty (on delete) or many characters when the user is pasting text.
let futureString: String = ((textField.text ?? "") as NSString).replacingCharacters(in: range, with: string)
// Determine if the change is allowed, update form, etc.
let allowChange = <#Your Validation Code#>
return allowChange
}
Add this code to the designated delegate of the textfield. Simple.