UITextField shouldChangeCharactersInRange fires twice? - ios

I have a table view. In cellForRowAtIndexPath I have a cell and in that cell there is UITextField. I set textfield's delegate like this: cell.textField.delegate = self;. I need to call my API on third character. So when user types 3 character in textfield, API is called hence shouldChangeCharactersInRange.
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
if (textField.text.length >= 2) {
NSString *substring = [NSString stringWithString:textField.text];
substring = [substring
stringByReplacingCharactersInRange:range withString:string];
[API CALLED WITH BLOCK WITH TEXTFIELD TEXT AS PARAMETER:substring];
}
return YES;
}
The problem is that when I type for example "abc" shouldChangeCharactersInRange is called first time and parameter is "abc". Second after, shouldChangeCharactersInRange is again called and my textfield has another added character which I did not type and it is always last character that is copied. So in this example, it sends "abcc". Do you know, what is the problem?

Setting breakpoints in that delegate method can sometimes cause the method to be fired twice. Try removing any breakpoints that are hit here or in your API method and test again.
This can be replicated easily. Create a new project, add a UITextField outlet and set the delegate to your controller. Implement textField:shouldChangeCharactersInRange: in your controller and set a breakpoint on an NSLog statement or something, and return YES. Sometimes, after telling the debugger to continue, a second keystroke will be generated and will hit your delegate method again.

I would recommend using a textFieldDidChange instead as this occurs after the text has been typed so you don't have to deal with appending strings. From there you can just check 'text.lenght >=3' to fire your API call.
You can add the event like this:
[textField addTarget:self
action:#selector(textFieldDidChange:)
forControlEvents:UIControlEventEditingChanged];
EDIT:
This code works for me. I sent up the delegate in the cell class.
#import "AnotherTableViewCell.h"
#implementation AnotherTableViewCell
#synthesize myTextField = _myTextField;
- (void)awakeFromNib {
// Initialization code
_myTextField.delegate=self;
[_myTextField addTarget:self
action:#selector(textFieldDidChange:)
forControlEvents:UIControlEventEditingChanged];
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
-(void)textFieldDidChange:(UITextField*)textField{
if (textField.text.length>=3) {
NSLog(#"Text >= 3: %#",textField.text);
}
}
#end

Related

if/else statement not being executed in UITextView delegate method

I have a if/else statement in my objective-c code. The if/else statement runs like this:
-(void)textViewDidBeginEditing:(UITextView *)textView
{
if(textView==self.heardTextView)
{
NSString *string = textView.text;
if ([string rangeOfString:#"CLOSER"].location == NSNotFound)
{
NSLog(#"closest");
}
}
}
The premise of the if/else statement is - if the textview equals a certain word some code will run. But this code isn't running.
I have put a breakpoint on my code and a NSLOG and nothing.
check your text field delegate if you not set your delegate then it's not called..
In your Viewcontroller.H
select your textview give delegate on textview.
First you need to set the delegate of the text view. You can do this with Interface Builder, but if you prefer using code you can do this in the viewDidLoad method of your view controller:
myTextView.delegate = self;
Now, when do you want the NSLog statement to be executed? Right now it looks like it will be run only if you type "CLOSER", then make the text view lose focus and then click on it again.
If you want the NSLog to run as you type, you should use:
-(BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
And also be careful, you need to calculate the next string value in the text field before testing its contents:
NSString *finalText = [textField.text stringByReplacingCharactersInRange:range withString:string];
Check your delegates, you must not have made the delegate of your textview as self. Check it once again. Or you might have done to some other class.

When Will textFieldDidEndEditing:textField be called?

I have 2 UITextField in a ViewController. One brings up a UIDatePicker another brings up a KeyBoard(number pad). I then assign user inputs to two class variables, a NSDate variable and a NSNumber variable, in the following method.
-(void)textFieldDidEndEditing:(UITextField *)textField{
if (textField == self.datePickerTextField){
//update xxtime
self.xxTime = self.datePicker.date;
NSLog(#"...%#",self.xxTime);
}
if (textField == self.numberTextField) {
int val = [textField.text intValue];
self.xxNumber = [NSNumber numberWithInt:val];
NSLog(#"...%#",self.xxNumber);
}
}
If I tap the datePickerTextField first then the numberTextField, this method won't get called after I finish typing in the numberTextField.
So my question is how do I make this method to get called? Should I specify "resignFirstResponder" somewhere?
TIA
It will invoke didEndEditing once another control gains focus
According to the API,
This method is called after the text field resigns its first responder
status.
So if you want didEndEditing to be invoked, you need to call
[textField resignFirstResponder];
Yo are right you need to specify resignFirstResponder
- (BOOL)textFieldShouldReturn:(UITextField *)textField {//Text Field Delegate
if (textField == textField1) {
[textField1 becomeFirstResponder];
}else{
[textField resignFirstResponder];
}
return TRUE; }
this can used with textFieldDidEndEditing also
Please check what iOS you are using. Remember that if you are using iOS 10, there is a method that is replacing the old textFieldDidEndEditing called func textFieldDidEndEditing(_ textField: UITextField, reason: UITextFieldDidEndEditingReason)

clearing a UITextField when the user starts typing

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

Capture text from UITextField in instance variable when another UIButton is pressed on UIView

I have a few UITextFields (within UITableViewCells) on my UIView with a "Save" UIButton. I want to do some basic validation on the UITextFields when the user clicks the "Save" button.
I have overridden textFieldDidEndEditing to save each of my UITextField data to an instance variable; however, if a user clicks the save button before either clicking the "Return" button of the UIKeyboard or clicking on another UITextField the data in my last UITextField is never saved to my instance variable and validation always fails.
I am looking for a way to trigger an "onBlur" (I know that's a JS thing)-type event to save my string in UITextField to my instance variable.
I've looked through the UITextFieldDelegate Protocol and I do not see anything like this.
Is there a method I may be missing?
to trigger textFieldDidEndEditing on your UITextField, you will need to call
[_txt resignFirstResponder];
were _txt is your UITextField
Please note that if you dont have a reference to _txt and you need to find the first responder in order to resign it
You could use the solution from this question Get the current first responder without using a private API
Then instead of calling
[_txt resignFirstResponder];
you would call
[self.view findAndResignFirstResponder];
Try this
// if we encounter a newline character return
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
// enter closes the keyboard
if ([string isEqualToString:#"\n"])
{
[textField resignFirstResponder];
return NO;
}
return YES;
}
which will trigger
- (BOOL)textFieldShouldEndEditing:(UITextField *)textField
{
[textField resignFirstResponder];
// Call webservice
return YES;
}

How can I get UITextFieldDelegate.shouldChangeCharactersInRange to fire with custom inputView?

I have a custom keyboard that I'm using to edit the text in mytextField and it works great. However, I can never get the shouldChangeCharactersInRange delegate to execute using my custom keyboard. It does execute when I use my actual keyboard(obviously not the default iPhone keyboard, since I'm set mytextField.inputView = numberPad.view). What should I do to cause the shouldChangeCharactersInRange delegate to fire using the custom keyboard? BTW, the custom keyboard is just a bunch of buttons.
- (void) numberPadPressed: (UIButton *)sender {
[mytextField insertText:[[NSNumber numberWithInt:sender.tag] stringValue]];
}
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
NSString *resultingString = [textField.text stringByReplacingCharactersInRange: range withString: string];
NSLog(#"shouldChangeCharactersInRange: %#", resultingString);
return true;
}
- (void)viewDidLoad {
mytextField.text = #"";
mytextField.inputView = numberPad.view;
mytextField.delegate = self;
[mytextField becomeFirstResponder];
}
You will never get the shouldChangeCharactersInRange called with a custom keyboard.
What you can get is the event UIControlEventEditingChanged of the UITextField. So instead of relying on shouldChangeCharactersInRange method (which is known to be buggy in addition), you should rely on this event, that will be fired each time the user changes the content of the text field.
[textField addTarget:self action:#selector(myTextfieldChangedMethod:) forControlEvents:UIControlEventEditingChanged];
If you modify yourself the text field with your custom keyboard, just call this method to notify all listeners of the event UIControlEventEditingChanged.
[textField sendActionsForControlEvents:UIControlEventTouchUpInside];
Edit: sorry, misunderstood your question previously.
Your text field calls shouldChangeCharactersInRange when it gets input from the system keyboard, giving you an opportunity to determine whether the text should actually display (for example you could implement it to recognize a shortcut and insert some longer text). But the text field doesn't recognize input from your custom view as text input.
If you have a custom inputView, you don't need to invoke this method--just update the text field directly from your IBAction:
- (IBAction) numberPadPressed: (UIButton *)sender {
NSString *newText = [[mytextField text] stringByReplacingCharactersInRange:[mytextField selectedRange] withString:string];
[mytextField setText:newText];
}
I know this is an old question but I ran into the same problem in needing to support pre 5.0 devices. If you override insertText: by following the instructions from Rob at http://dev.ragfield.com/2009/09/insert-text-at-current-cursor-location.html then you will get shouldChangeCharactersInRange to fire every time.
If you then need to support the backspace key like I did you can use the answer from #cormacrelf at How to simulate the delete key on UITextField?
The only snafu I haven't been able to work around is that when you delete a character from the middle of the string the cursor jumps back to the end of the string.

Resources