The next code will work only when the user will enter the third character. I want the focus to move TextField when the user enters the second character but still keep the first 2 characters in the first TextField. if I'll try something likenewString.length < 2 , when entering 2 characters in a row I'll get the first character in the first UITextField and the second character in the second UITextField.
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
NSString *newString = [textField.text stringByReplacingCharactersInRange:range withString:string];
if (newString.length <= 2 )
{
return YES;
}
else
{
NSInteger nextTag;
nextTag = textField.tag + 1;
UIResponder* nextResponder = [textField.superview viewWithTag:nextTag];
if (nextResponder) {
[nextResponder becomeFirstResponder];
}
else {
[textField resignFirstResponder];
}
}
return YES;
}
I belive this should work:
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
NSString *newString = [textField.text stringByReplacingCharactersInRange:range withString:string];
if(newString.length == 2)
{
UIResponder* nextResponder = [textField.superview viewWithTag:textField.tag + 1];
if (nextResponder) {
[nextResponder performSelector:#selector(becomeFirstResponder) withObject:nil afterDelay:0.1];
}
else {
[textField resignFirstResponder];
}
}
return newString.length <= 2;
}
Please try it and tell me if it works. If not please tell me a bit more about your setup and I will try to set up a view like yours and it will be easier for me to try solutions.
Ok, EDIT The above works. It is not very beautiful but it does the job. It is sad that apple would not add a method for didChangeCharactersInRange which would be much more appropriate for this.
EDIT Edited according to Sha's findings 0.1 instead of 0 in the timer interval
EDIT Changed according to Mike's suggestion
Related
There is a UITextField with name numbercontent, after entering 8 numbers, it will automatically call the next function. Following is my code
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
if (self.numbercontent.text.length == 7) {
[self.numbercontent resignFirstResponder];
[self stopFly];
}
return YES;
}
But there's a bug
when I enter the 8th number, it will automatically call the next function, but the 8th number isn't shown in the UITextField.
If Change self.numbercontent.text.length == 7 to self.numbercontent.text.length > 7, the 8th number is shown in the UITextField, but I need to enter one more number to call the next function, how to fix this bug, thanks.
Try this, instead of shouldChangeCharactersInRange,
[_txtNum addTarget:self action:#selector(didChangeText:) forControlEvents:UIControlEventEditingChanged];
and then add this method,
-(void)didChangeText:(UITextField*)sender
{
if(sender.text.length==8)
{
[self stopFly];
[self.txtNum resignFirstResponder];
}
}
Although answer suggested by #DhavalBhimani is the standard way to handle this, but alternatives can be used with current approach like:
In Objective-C:
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
NSString *updatedString = [textField.text stringByReplacingCharactersInRange:range withString:string];
if (textField.text.length == 7) {
textField.text = updatedString;
[textField resignFirstResponder];
[self stopFly];
}
return YES;
}
In Swift 4.0:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let updatedString = (textField.text as NSString?)?.replacingCharacters(in: range, with: string)
if textField.text?.count == 7 {
textField.text = updatedString
self.view.endEditing(true)
}
return true
}
Use this.
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string{
// _textLenghtLimit = your text max lenght
if (_textLenghtLimit > 0) {
if ((range.location >= _textLenghtLimit || textField.text.length + 1 > _textLenghtLimit) && range.length == 0) {
return NO;
}
}
return YES;
}
So I have been working on a small requirement that has taken me way more time than I would like, as small requirements in UIKit seem to do sometimes:
When a user enters a password longer than 3 characters you change the
keyboard to have a done button.
Simple enough... it seems that KVO isn't fired until editing ends, and neither is the textFieldDidEndEditing: delegate method called. Ok, easy enough, just do our logic in the -(BOOL)textField:shouldChangeCharactersInRange:replacementString: delegate callback...
Attempt A:
-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
NSString * newString = [textField.text stringByReplacingCharactersInRange:range withString:string];
if (newString.length >=3)
{
textField.returnKeyType = UIReturnKeyGo;
}
return YES;
}
Attempt A, does nothing... never changes the keyboard to Go
Attempt B:
-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
NSString * newString = [textField.text stringByReplacingCharactersInRange:range withString:string];
if (newString.length >=3)
{
textField.returnKeyType = UIReturnKeyGo;
[textField resignFirstResponder];
[textField becomeFirstResponder];
}
return YES;
}
Attempt B: yay, keyboard button changes, but when we resignFirstResponder, we discard the new input so the User can't enter their password... bad
Attempt C:
-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
NSString * newString = [textField.text stringByReplacingCharactersInRange:range withString:string];
if (newString.length >=3)
{
textField.returnKeyType = UIReturnKeyGo;
}else{
textField.returnKeyType = UIReturnKeyDefault;
}
textField.text = newString;
[textField resignFirstResponder];
[textField becomeFirstResponder];
return NO;
}
Attempt C is tricky, we return NO, telling the delegate not to accept the edit, but that is ok, because we explicitly set the string in the delegate (this seems like sort of a bad idea), but it all works, except that when you resign firstResponder status it changes your keyboard (if you had the number keyboard up, it will switch to default after every keystroke)
Attempt D:
-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
NSString * newString = [textField.text stringByReplacingCharactersInRange:range withString:string];
if (newString.length >=3)
{
textField.returnKeyType = UIReturnKeyGo;
}else{
textField.returnKeyType = UIReturnKeyDefault;
}
//detect if we have crossed a boundry
if ((textField.text.length >=3) != (newString.length >=3))
{
[textField resignFirstResponder];
[textField becomeFirstResponder];
}
textField.text = newString;
return NO;
}
Attempt D is pretty good, it will only resign first responder as you cross the 2/3 or 3/2 edge so you only lose your keyboard once, not a big deal usually
so the question, what is the best practice way to do this?
(resigning first responder only seems to cancel the edit if using secure input, if you aren't familiar with this problem), I have also prepared a sample project, as to help anyone that wants to look at it: sample project
That's far too much work. Just make yourself the delegate of the UITextView and you will get TextViewDidChange messages.
- (void)textViewDidChange:(UITextView *)textView;
Or if using a UITextField register for its UITextFieldTextDidChangeNotification message.
add the delegate on text did change
[textField addTarget:self action:#selector(textFieldDidChange:) forControlEvents:UIControlEventEditingChanged];
-(void)textFieldDidChange :(UITextField *)theTextField
{
enter your logic here
}
Try this
textField.returnKeyType = UIReturnKeyNext;
I have a Client Details screen with many UITextField. I need to limit the postcodeField to a maximum of 7 characters and convert to uppercase automatically. I already have code to convert the text to uppercase, but it seems I cannot do anything else with that particular UITextField in its Delegatemethod
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
Here is what I have tried:
#define MAXLENGTH 7
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
if (textField == self.postcodeField) {
self.postcodeField.text = [textField.text stringByReplacingCharactersInRange:range withString:[string uppercaseString]];
return NO;
}
if (self.postcodeField.text.length >= MAXLENGTH && range.length == 0)
{
return NO;
}
return YES;
}
And:
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
if (textField == self.postcodeField) {
self.postcodeField.text = [textField.text stringByReplacingCharactersInRange:range withString:[string uppercaseString]];
return NO;
}
NSUInteger newLength = [textField.text length] + [string length] - range.length;
return (newLength > 7) ? NO : YES;
}
This code does not work. I know there are many threads with various solutions to setting a maximum length, but I can't find a solution that caters for uppercase conversion too. I am quite new to iOS so I apologise if this is seen as a duplicate post. Any help is much appreciated!
This will surly help to restrict to 7 Characters.
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
[textField setText:[textField.text uppercaseString]];
NSUInteger newLength = [textField.text length] + [string length] - range.length;
return (newLength > 7) ? NO : YES;
}
In my opinion the better approach to your problem is to use NSNotificationCenter with UITextFieldTextDidChangeNotification
Then you can add this code to viewDidLoad:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(maxLength:)
name:UITextFieldTextDidChangeNotification
object:self.postcodeField];
then, you only need to add the selector method, e.g.:
- (void) maxLength: (NSNotification*) notification
{
UITextField *notificationTextField = [notification object];
if (notificationTextField == self.postcodeField)
{
if (self.postcodeField.text.length >= MAXLENGTH)
{
// remove here the extra text
}
}
}
I have run this code and its working fine, so try it out
-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
if(textField==self.txtPostCodeField)
{
int limit = 100;
return !([textField.text length]>=limit && [string length] >= range.length);
}
return YES;
}
I have decided to implement a new method for achieving what I want. My UITextField delegate method - (BOOL)textField: shouldChangeCharactersInRange: replacementString: was getting very messy as more textfields were added to the view, all of which are doing different things. So I have created a subclass to use on the desired Postcode field. I wasn't able to use the posted solution in a subclass of UITextField as it is bad to set the delegate to self within that subclass (a known issue with UITextField subclassing - see this, this, and this.). The new code is more efficient than the answer I had previously accepted, and can be widely adapted to do many things.
The header file:
PostcodeField.h
#interface PostcodeField : UITextField
- (BOOL)stringIsAcceptable:(NSString *)string inRange:(NSRange)range;
#end
The subclass implementation (another requirement was to only accept specified characters which has been easily implemented):
PostcodeField.m
#define ACCEPTABLE_CHARACTERS #" ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
#define CHARACTER_LIMIT 8
#implementation PostcodeField
- (BOOL)stringIsAcceptable:(NSString *)string inRange:(NSRange)range {
NSUInteger newLength = [self.text length] + [string length] - range.length;
// Check text meets character limit
if (newLength <= CHARACTER_LIMIT) {
// Convert characters to uppercase and return acceptable characters
NSCharacterSet *cs = [[NSCharacterSet characterSetWithCharactersInString:ACCEPTABLE_CHARACTERS] invertedSet];
NSString *filtered = [[string componentsSeparatedByCharactersInSet:cs] componentsJoinedByString:#""];
[self setText:[self.text stringByReplacingCharactersInRange:range withString:[filtered uppercaseString]]];
}
return NO;
}
Then I set the delegate to self on the desired textfield within it's ViewController:
self.postcodeField.delegate = self;
And call it's delegate method on the ViewController:
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
// Call PostcodeField subclass on Postcode textfield
if ([textField isKindOfClass:[PostcodeField class]]) {
return [(PostcodeField *)textField stringIsAcceptable:string inRange:range];
}
return YES;
}
And of course, importing the subclass on the ViewController:
#import "PostcodeField.h"
You can set the textfield to use a subclass by navigating to the "Identity Inspector" using IB (Interface Builder) and setting the Custom Class to your subclass:
By using subclasses, your UITextField delegate method can be cleaner and more efficient, and the subclass can be called on as many textfields as you like. If there are multiple textfields on that view, just follow the same process and test for each subclass within the UITextField delegate method. I hope this post will be helpful for anyone wanting to utilise subclasses on UITextFields.
In Swift you can use the code below to do it.
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool
{
if(range.length + range.location > countElements(textField.text))
{
return false
}
textField.autocapitalizationType = UITextAutocapitalizationType.AllCharacters // Capitalize the all characters automatically
var newLength: Int = countElements(textField.text) + countElements(string) - range.length
return (newLength > 7) ? false : true
}
i have four textfields. User is allowed to enter only one digit in each text field.Once the user Enters single digit its focus should be on the next textfield. i have done this part and its working fine. Now, What i want is when i remove the text from the text field then its focus should move to previous textfield. i mean if i am deleting the text(digit) from the fourth text field then its focus should move to third text field. In short on removal of text the focus should be on previous textfield.
Now, what my problem is when i remove the text from the textfield then its focus moves to previous textfield but it clears the text of that textfield(the textfield on which i have set the focus).What i want is the text should not be removed on focus.
in .h file
IBOutlet UITextField *txtPinDigit1;
IBOutlet UITextField *txtPinDigit2;
IBOutlet UITextField *txtPinDigit3;
IBOutlet UITextField *txtPinDigit4;
UITextField *currentTextField;
in .m file
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField
{
if(textField.tag==0)
{
currentTextField=txtPinDigit1;
}
else if(textField.tag==1)
{
currentTextField=txtPinDigit2;
}
else if(textField.tag==2)
{
currentTextField=txtPinDigit3;
if(isDelete)
{
textField.text=digit3;
}
}
else if(textField.tag==3)
{
currentTextField=txtPinDigit4;
}
return YES;
}
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
if(([textField.text length]==1)&&(![string isEqualToString:#""]))
{
if(currentTextField==txtPinDigit1)
{
[txtPinDigit2 becomeFirstResponder];
}
else if(currentTextField==txtPinDigit2)
{
[txtPinDigit3 becomeFirstResponder];
}
else if(currentTextField==txtPinDigit3)
{
[txtPinDigit4 becomeFirstResponder];
}
else if(currentTextField==txtPinDigit4)
{
textField.text = [textField.text substringToIndex:MAXLENGTH-1];
//[txtPinDigit4 resignFirstResponder];
//[txtPinDigit1 becomeFirstResponder];
}
}
else if([string isEqualToString:#""])
{
isDelete=YES;
NSLog(#"replacementString:%#",string);
// textField.text=string;
if(currentTextField==txtPinDigit4)
{
textField.text=string;
digit3=nil;
[digit3 release];
digit3=[[NSString alloc] initWithFormat:#"%i",[txtPinDigit3.text intValue]];
[txtPinDigit3 becomeFirstResponder];
}
else if(currentTextField==txtPinDigit3)
{
textField.text=string;
[txtPinDigit2 becomeFirstResponder];
}
else if(currentTextField==txtPinDigit2)
{
textField.text=string;
[txtPinDigit1 becomeFirstResponder];
}
else if(currentTextField==txtPinDigit1)
{
textField.text=string;
// [txtPinDigit1 resignFirstResponder];
//[txtPinDigit1 becomeFirstResponder];
}
}
return YES;
}
in the above code MAXLENGTH=1 defined.
any help will be appreciated.
thanks in advance.
Try This code, Change shouldChangeCharactersInRange: Delegate Method
-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
NSString *newString = [textField.text stringByReplacingCharactersInRange:range withString:string];
if ([newString length] < 1)
{
if (textField.tag==2)
{
[txtPinDigit1 performSelector:#selector(becomeFirstResponder) withObject:nil afterDelay:0.0];
// txt2.text=#"";
}
else if (textField.tag==3)
{
[txtPinDigit2 performSelector:#selector(becomeFirstResponder) withObject:nil afterDelay:0.0];
}
else if (textField.tag==4)
{
[txtPinDigit3 performSelector:#selector(becomeFirstResponder) withObject:nil afterDelay:0.0];
}
return YES;
} else
{
// Otherwise we cut the length of newString to 1 (if needed) and set it to the textField.
textField.text = [newString length] > 1 ? [newString substringToIndex:1] : newString;
if (textField.tag==1)
{
[textField resignFirstResponder];
[txtPinDigit2 becomeFirstResponder];
}
else if (textField.tag==2)
{
[textField resignFirstResponder];
[txtPinDigit3 becomeFirstResponder];
}
else if (textField.tag==3)
{
[textField resignFirstResponder];
[txtPinDigit4 becomeFirstResponder];
}
return NO;
}
}
Check to check length via this way and put a break point at beginning of shouldChangeCharactersInRange
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
int length = textField.text.length - range.length + string.length;
return YES;
}
I hope it will help you.
Before calling up to become first responder on filling up the 1st digit,bind the entered text inputs & the responder into dictionary...
On each previous/next condition,retrieve the information from the dictionary...The below example code is for the general previous/next custom actions(not auto though)..You just need to change the logic a bit based on the condition you prefer...
- (NSArray *) responders
{
if (_responders)
return _responders;
NSArray *textInputs = EditableTextInputsInView([[UIApplication sharedApplication] keyWindow]);
return [textInputs sortedArrayUsingComparator:^NSComparisonResult(UIView *textInput1, UIView *textInput2) {
UIView *commonAncestorView = textInput1.superview;
while (commonAncestorView && ![textInput2 isDescendantOfView:commonAncestorView])
commonAncestorView = commonAncestorView.superview;
CGRect frame1 = [textInput1 convertRect:textInput1.bounds toView:commonAncestorView];
CGRect frame2 = [textInput2 convertRect:textInput2.bounds toView:commonAncestorView];
return [#(CGRectGetMinY(frame1)) compare:#(CGRectGetMinY(frame2))];
}];
}
The below function should get called before doing previous/next auto tab.
- (Void) selectAdjacentResponder: (UISegmentedControl *) sender
{
NSArray *firstResponders = [self.responders filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(UIResponder *responder, NSDictionary *bindings) {
return [responder isFirstResponder];
}]];
NSLog(#"%#",firstResponders);
UIResponder *firstResponder = [firstResponders lastObject];
NSInteger offset = sender.selectedSegmentIndex == 0 ? -1 : +1;
NSInteger firstResponderIndex = [self.responders indexOfObject:firstResponder];
NSInteger adjacentResponderIndex = firstResponderIndex != NSNotFound ? firstResponderIndex + offset : NSNotFound;
UIResponder *adjacentResponder = nil;
if (adjacentResponderIndex >= 0 && adjacentResponderIndex < (NSInteger)[self.responders count])
adjacentResponder = [self.responders objectAtIndex:adjacentResponderIndex];
[adjacentResponder becomeFirstResponder];
}
Bit lengthier,but this is all i know a BIT :)..Happy coding...
i have 6 textfields named digit1,digit2..etc upto digit6 added as a subview over a view. i want the digit2 textfield to autofocus once the user enters a digit in the digit1 textfield and similarly digit3 should autofocus when a digit is entered in digit2 textfield.Below shown is the code i tried.
-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
if(textField.text.length>=1)
{
[textField resignFirstResponder];
UITextField *_textField=(UITextField*) [self.view viewWithTag:textField.tag+1];
[_textField becomeFirstResponder];
}
return TRUE;
}
What happens here is when i enter a digit in digit1 it dosent appear in digit 1 but it appears in digit2 and also when i click delete button the control is transfered to the subsequent textfields rather than deleting the text in the current textfield.Please help me to fix this.
lets assume that you have 3 textfields with names such as t1, t2 and t3
set tag value for t1 = 1, t2 = 3 and t3 = 3
set tag values for all the textfields and the write this code
then write this code
-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
switch(textField.tag){
case 1:{
if(textField.text.length>=1)
{
[t1 resignFirstResponder];
[t2 becomeFirstResponder];
break;
}
}
case 2:{
if(textField.text.length>=1)
{
[t2 resignFirstResponder];
[t3 becomeFirstResponder];
break;
}
}
case 3:{
if(textField.text.length>=1)
{
[t3 resignFirstResponder];
break;
}
}
}
}
return TRUE;
}
I think the problem is that shouldChangeCharactersInRange gets called before the edit actually occurs. Since you are changing the first responder in there, the edit takes effect in the new first responder instead of the old one.
You could try moving the code inside your if block to another method, and calling it with [self performselector:withObject:afterDelay], setting the delay to some small value. Something like:
-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
if(textField.text.length>=1)
{
[self performSelector:#selector(switchTextField) withObject:nil afterDelay:0.1];
}
return TRUE;
}
-(void)switchTextField
{
[textField resignFirstResponder];
UITextField *_textField=(UITextField*) [self.view viewWithTag:textField.tag+1];
[_textField becomeFirstResponder];
}
That way shouldChangeCharactersInRange returns true immediately (as it should), the edit will take place in the currently selected textField, and then your switchTextField method will get called soon after. It's not the cleanest solution, but it's the quickest I can think of off the top of my head.
I found the solution
-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
NSString *str = [textField.text stringByReplacingCharactersInRange:range withString:string];
if( [str length] > 0 ){
textField.text = string;
UIResponder* nextResponder = [textField.superview viewWithTag:(textField.tag + 1)];
if (nextResponder) {
[nextResponder becomeFirstResponder];
}
return NO;
}
return YES;
}