My application allows the user to enter a numeric value (currency) in a UITextField control, but the keyboard layout that I wish was available is unfortunately not one of the built-in options, so I had to choose the "Numbers & Punctuation" option in Interface Builder. Here's the corresponding dialog window in IB:
So when my application asks the user for the input, it is displaying the following:
Which is perfectly fine, but look at all of the extra keys available to the user! One could easily enter "12;56!" in the text field, and I assume I have to validate that somehow.
So my question is: how do I validate currency values entered into a UITextField?
I have the urge to answer because this was the first entry I saw when I googled and the highest ranked answer wouldn't allow me to enter a currency.
I'm german, and in germany (and in many other countries) we use , as the decimalseparator.
I just wrote a similar method and this is what I have right now.
- (BOOL) textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
static NSString *numbers = #"0123456789";
static NSString *numbersPeriod = #"01234567890.";
static NSString *numbersComma = #"0123456789,";
//NSLog(#"%d %d %#", range.location, range.length, string);
if (range.length > 0 && [string length] == 0) {
// enable delete
return YES;
}
NSString *symbol = [[NSLocale currentLocale] objectForKey:NSLocaleDecimalSeparator];
if (range.location == 0 && [string isEqualToString:symbol]) {
// decimalseparator should not be first
return NO;
}
NSCharacterSet *characterSet;
NSRange separatorRange = [textField.text rangeOfString:symbol];
if (separatorRange.location == NSNotFound) {
if ([symbol isEqualToString:#"."]) {
characterSet = [[NSCharacterSet characterSetWithCharactersInString:numbersPeriod] invertedSet];
}
else {
characterSet = [[NSCharacterSet characterSetWithCharactersInString:numbersComma] invertedSet];
}
}
else {
// allow 2 characters after the decimal separator
if (range.location > (separatorRange.location + 2)) {
return NO;
}
characterSet = [[NSCharacterSet characterSetWithCharactersInString:numbers] invertedSet];
}
return ([[string stringByTrimmingCharactersInSet:characterSet] length] > 0);
}
While you can putz around in the NSNumberFormatter, I found it easier to just screen out anything but 0-9 and .
This is working well for me:
- (BOOL) textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
NSCharacterSet *nonNumberSet = [[NSCharacterSet characterSetWithCharactersInString:#"0123456789."] invertedSet];
return ([string stringByTrimmingCharactersInSet:nonNumberSet].length > 0);
}
I found this to be a relatively clean approach. I haven't tested it with non-US currencies but since it uses the NSNumberFormatter's properties I believe it should handle them correctly.
Start by setting up a formatter some place:
formatter = [NSNumberFormatter new];
[formatter setNumberStyle: NSNumberFormatterCurrencyStyle];
[formatter setLenient:YES];
[formatter setGeneratesDecimalNumbers:YES];
Use the formatter to parse and reformat their input. It also handles shifting the number when digits are added and removed.
-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
NSString *replaced = [textField.text stringByReplacingCharactersInRange:range withString:string];
NSDecimalNumber *amount = (NSDecimalNumber*) [formatter numberFromString:replaced];
if (amount == nil) {
// Something screwed up the parsing. Probably an alpha character.
return NO;
}
// If the field is empty (the initial case) the number should be shifted to
// start in the right most decimal place.
short powerOf10 = 0;
if ([textField.text isEqualToString:#""]) {
powerOf10 = -formatter.maximumFractionDigits;
}
// If the edit point is to the right of the decimal point we need to do
// some shifting.
else if (range.location + formatter.maximumFractionDigits >= textField.text.length) {
// If there's a range of text selected, it'll delete part of the number
// so shift it back to the right.
if (range.length) {
powerOf10 = -range.length;
}
// Otherwise they're adding this many characters so shift left.
else {
powerOf10 = [string length];
}
}
amount = [amount decimalNumberByMultiplyingByPowerOf10:powerOf10];
// Replace the value and then cancel this change.
textField.text = [formatter stringFromNumber:amount];
return NO;
}
Perhaps you could attach a UITextFieldDelegate on the control and have it implement textField:shouldChangeCharactersInRange:replacementString: That way, if it sees any characters that you don't want in the field, it can reject them.
In conjunction with the textField:shouldChangeCharactersInRange:replacementString: suggestion made by Marc, you should pass the text through an NSNumberFormatter using an NSNumberFormatterCurrencyStyle. This will handle the quirks of currency formatting and handle locale specific options.
There's a "Data Formatting Programming Guide for Cocoa" section in the iPhone documentation if you search for it. Sadly, most of the UI information here is Mac OS X specific (doesnt work on iPhone) but it'll show you how to use the formatter classes.
I found that the shouldChangeCharactersInRange screws up the pop-up keyboard, backspace and "Done" button as well. I found if I handled 0 length strings and allowed control characters though, it worked fine.
I don't like using NSNumberFormatter because it insists that the number is well-formed at all stages while the user is editing and that can be infuriating if you, say, want to have two decimal points in the number for a moment until you delete the one that's in the wrong spot.
Here's the code I used:
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
if ([string length] < 1) // non-visible characters are okay
return YES;
if ([string stringByTrimmingCharactersInSet:[NSCharacterSet controlCharacterSet]].length == 0)
return YES;
return ([string stringByTrimmingCharactersInSet:[self.characterSet invertedSet]].length > 0);
}
Where self.characterSet holds the characters that are acceptable, I used this method to create it for a currency:
- (NSCharacterSet *)createCurrencyCharacterSet
{
NSLocale *locale = [NSLocale currentLocale];
NSMutableCharacterSet *currencySet = [NSMutableCharacterSet decimalDigitCharacterSet];
[currencySet addCharactersInString:#"-"]; // negative symbol, can't find a localised version
[currencySet addCharactersInString:[locale objectForKey:NSLocaleCurrencySymbol]];
[currencySet addCharactersInString:[locale objectForKey:NSLocaleGroupingSeparator]];
[currencySet addCharactersInString:[locale objectForKey:NSLocaleDecimalSeparator]];
return [[currencySet copy] autorelease];
}
The somewhat unhappy code [[currencySet copy] autorelease] returns an immutable NSCharacterSet.
Using [NSCharacterSet decimalDigitCharacterSet] also includes the Indic and Arabic equivalent characters which hopefully means that people use those languages can use their alphabet's digits to enter numbers.
It's still necessary to check that NSNumberFormatter can parse the user's input and alert if it can't; nonetheless, it makes a nicer experience when only legit characters can be entered.
I keep seeing that people don't know how to properly determine the resulting string when implementing shouldChangeCharactersInRange.
Here's how you get the new text that would be entered if you returned YES:
NSString *newText = [textField.text stringByReplacingCharactersInRange:range withString:string];
Once you have this new text, it's easy to check if that text is a valid number or not, and return YES or NO accordingly.
Keep in mind that all other solutions, such as those shown here, where one checks the length of "string", and such, may not work properly if the user tries to edit the string using a bluetooth keyboard with cursor keys or using the advanced editing features (select, cut, paste).
If you want them to only be able to enter numbers - you might also consider the number keypad
After finding a quick solution on stack overflow to handle US currency, I rewrote the function to safely handle international currencies as well. This solution will dynamically validate user input from a UITextField, correcting as they type.
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
NSNumberFormatter *nf = [[NSNumberFormatter alloc] init];
[numberFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4];
[numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
[numberFormatter setMaximumFractionDigits:2];
[numberFormatter setMinimumFractionDigits:0];
// Grab the contents of the text field
NSString *text = [textField text];
// the appropriate decimalSeperator and currencySymbol for the current locale
// can be found with help of the
// NSNumberFormatter and NSLocale classes.
NSString *decimalSeperator = numberFormatter.decimalSeparator;
NSString *currencySymbol = numberFormatter.currencySymbol;
NSString *replacementText = [text stringByReplacingCharactersInRange:range withString:string];
NSMutableString *newReplacement = [[ NSMutableString alloc ] initWithString:replacementText];
// whenever a decimalSeperator or currencySymobol is entered, we'll just update the textField.
// whenever other chars are entered, we'll calculate the new number and update the textField accordingly.
// If the number can't be computed, we ignore the new input.
NSRange decimalRange = [text rangeOfString:decimalSeperator];
if ([string isEqualToString:decimalSeperator] == YES &&
[text rangeOfString:decimalSeperator].length == 0) {
[textField setText:newReplacement];
} else if([string isEqualToString:currencySymbol] == YES&&
[text rangeOfString:currencySymbol].length == 0) {
[textField setText:newReplacement];
} else if([newReplacement isEqualToString:currencySymbol] == YES) {
return YES;
}else {
NSString *currencyGroupingSeparator = numberFormatter.currencyGroupingSeparator;
[newReplacement replaceOccurrencesOfString:currencyGroupingSeparator withString:#"" options:NSBackwardsSearch range:NSMakeRange(0, [newReplacement length])];
[numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
NSNumber *number = [numberFormatter numberFromString:newReplacement];
if([newReplacement length] == 1) {
[numberFormatter setNumberStyle:NSNumberFormatterDecimalStyle];
number = [numberFormatter numberFromString:newReplacement];
}
if (number == nil) {
[newReplacement release];
return NO;
}
[numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
text = [numberFormatter stringFromNumber:number];
[textField setText:text];
}
[newReplacement release];
return NO; // we return NO because we have manually edited the textField contents.
}
Using an NSNumberFormatter is the correct answer. It will handle validation and converting the string to and from the correct object type.
You can also use Gamma-Point solution and evaluate if *aNumber is Nil, if its Nil, then it means a character that wasnt 0-9 . or , was entered, this way you can validate just numbers, it will nil the variable if any no-number char is entered.
I Couldn't find the appropriate implementation for currency validate. Here is my variant:
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
NSString* proposedString = [textField.text stringByReplacingCharactersInRange:range withString:string];
//if there is empty string return YES
if([proposedString length] == 0) {
return YES;
}
//create inverted set for appripriate symbols
NSCharacterSet *nonNumberSet = [[NSCharacterSet characterSetWithCharactersInString:#"0123456789.,"] invertedSet];
//if side symbols is trimmed by nonNumberSet - return NO
if([proposedString stringByTrimmingCharactersInSet:nonNumberSet].length != [proposedString length]) {
return NO;
}
//if there is more than 1 symbol of '.' or ',' return NO
if([[proposedString componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:#".,"]] count] > 2) {
return NO;
}
//finally check is ok, return YES
return YES;
}
Using shouldChangeCharactersInRange screws up the pop-up key board as the backspace button doesn't work.
Number formatter is the way to go. Here's a sample I used to find positive decimals. I call it during the validation check- for e.g. when the user clicks on the save button.
-(BOOL) isPositiveNumber: (NSString *) numberString {
NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
[numberFormatter setNumberStyle:NSNumberFormatterDecimalStyle];
NSNumber *aNumber = [numberFormatter numberFromString:numberString];
[numberFormatter release];
if ([aNumber floatValue] > 0) {
NSLog( #"Found positive number %4.2f",[aNumber floatValue] );
return YES;
}
else {
return NO;
}
}
Related
I want to format the textfield text when user enter a value .. It work perfectly but my problem is ,If user press 1 then 2 after that decimale point . My replacement string is "12." At that time numbar is 12 . i am not able to take decimal point ..
NSNumber* number = [numberFormatter numberFromString:string];
Here is My full code,
#pragma mark textfieldDelgate
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
NSLocale *locale=[[NSLocale alloc]initWithLocaleIdentifier:#"en_GB"];
NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
[numberFormatter setLocale:locale];
[numberFormatter setNumberStyle:NSNumberFormatterDecimalStyle];
NSString *text = [textField text];
NSString *replacementText = [text stringByReplacingCharactersInRange:range withString:string];
NSMutableString *newReplacement = [[ NSMutableString alloc ] initWithString:replacementText];
NSString *currencyGroupingSeparator = numberFormatter.currencyGroupingSeparator;
[newReplacement replaceOccurrencesOfString:currencyGroupingSeparator withString:#"" options:NSBackwardsSearch range:NSMakeRange(0, [newReplacement length])];
NSNumber * number = [numberFormatter numberFromString:newReplacement];
if (number == nil && [replacementText length]!=0) {
return NO;
}
NSLog(#"%#",newReplacement);
text = [numberFormatter stringFromNumber:number];
[textField setText:text];
return NO;
}
I have also attached screenshot.
How can I format the string with decimal value......
Thanks in advance
For me the line of code:
NSNumber * number = [numberFormatter numberFromString:newReplacement];
does not returns nil but it removes the decimal entered by user. This is because with numberFormatter id you pass a value like '12.' or '12.0' it will remove the decimal as a part of formatting. For other values like 12.3 it will not remove decimal.
I am not able to understand your requirement for doing all this coding but if you want decimal to be there put some logic to check if number is in format of 12. or 12.0 , then escape formatting. Decimal will remain there.
Please use this code:
NSNumber * number;
if ([newReplacement hasSuffix:#"."] || [newReplacement hasSuffix:#".0"])
{
[textField setText:newReplacement]; return NO;
}
else
{
number = [numberFormatter numberFromString:newReplacement];
}
You'll probably want to use NSNumberFormatterCurrencyStyle instead of NSNumberFormatterDecimalStyle if you're dealing with formatting money. It might Just Work™ after setting that.
But, it might not. Regardless of all that, pretty sure the reason for the behavior you're seeing is that you're not giving it a number from your string; 12 is a number, as is 12.5, but, 12. is not. I'm surprised its not returning nil outright.
If thats the case (and its still broken), than you'll probably want to special-case having the . at the end, and append instead of passing it into the number formatter.
I have tried printing your code in my log. It is working fine for me.I think there is some other type of issue.
NSLocale *locale=[[NSLocale alloc]initWithLocaleIdentifier:#"en_GB"];
NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
[numberFormatter setLocale:locale];
[numberFormatter setNumberStyle:NSNumberFormatterDecimalStyle];
NSNumber* number = [numberFormatter numberFromString:#"12.4"];
NSLog(#"number is %#",number);
log print-----
number is 12.4
//Add this in .h file
NSNumberFormatter *formatter;
NSInteger currencyScale;
NSString *enteredDigits;
//Add this in .m file`enter code here`
-(void)viewdidload
{
NSNumberFormatter *aFormatter = [[NSNumberFormatter alloc] init];
[aFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
currencyScale = -1 * [aFormatter maximumFractionDigits];
self.formatter = aFormatter;
}
#pragma UItextfield Delegate
- (void)textFieldDidBeginEditing:(UITextField *)textField {
// Keep a pointer to the field, so we can resign it from a toolbar
Field = textField;
if(textField.text==NULL || [textField.text isEqualToString:#""])
{
self.enteredDigits = #"";
//textField.text=#"";
}
}
- (void)textFieldDidEndEditing:(UITextField *)textField {
}
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string { //give tag to text field
if (textField.tag==10) {
// self.priceInput = textField;
// Check the length of the string
int len=[self.enteredDigits length];
NSLog(#"string lenght is %d",len);
if ([string length]) {
self.enteredDigits = [self.enteredDigits stringByAppendingFormat:#"%d", [string integerValue]];
} else {
// This is a backspace
NSUInteger len = [self.enteredDigits length];
if (len > 1) {
self.enteredDigits = [self.enteredDigits substringWithRange:NSMakeRange(0, len - 1)];
} else {
self.enteredDigits = #"";
}
}
NSDecimalNumber *decimal = nil;
if ( ![self.enteredDigits isEqualToString:#""]) {
decimal = [[NSDecimalNumber decimalNumberWithString:self.enteredDigits] decimalNumberByMultiplyingByPowerOf10:currencyScale];
float deci= [decimal floatValue];
NSLog(#"decimall==%f",deci);
} else {
decimal = [NSDecimalNumber zero];
}
// Replace the text with the localized decimal number
float deci= [decimal floatValue];
NSLog(#"decimall==%f",deci);
NSString *temp = [self.formatter stringFromNumber:decimal];
textField.text=temp;
//NSLog(#"Text fielddddddddddddddf=%# %#",numberText,temp);
return NO;
}
return YES;
}
- (BOOL)textFieldShouldClear:(UITextField *)textField
{
textField.text=#"";
self.enteredDigits=#"";
NSLog(#"Clear Clicked");
return YES;
}
I think this code will help you
I have a textfield that I want to input in it with numpad keyboard.
I want the textfield to be like 00.00 at first, and as the user strat typing it update from left to right, or right to left. e.g if he types 2 followed by 3 then textfield should be like 23.00, and also the user just allowed to input a number with at most 2 digits after decimal point.
How can I get this functionallity??
Thanks in Advanced.
Cheers,
Siavash
In .h
#property (assign, nonatomic) int maximumFractionDigits;
#property (strong, nonatomic) NSString* decimalSeparator;
In loadView or viewDidLoad put this,
NSNumberFormatter* numberFormatter = [[NSNumberFormatter alloc] init];
[numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
[numberFormatter setCurrencyCode:#"GBP"];
self.maximumFractionDigits = numberFormatter.maximumFractionDigits;
self.decimalSeparator = numberFormatter.decimalSeparator;
[numberFormatter release];
In UITextFieldDelegate method
- (BOOL)textField:(UITextField*)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString*)string {
// get current cursor position
UITextRange* selectedRange = [textField selectedTextRange];
UITextPosition* start = textField.beginningOfDocument;
NSInteger cursorOffset = [textField offsetFromPosition:start toPosition:selectedRange.start];
// Update the string in the text input
NSMutableString* currentString = [NSMutableString stringWithString:textField.text];
NSUInteger currentLength = currentString.length;
[currentString replaceCharactersInRange:range withString:string];
// Strip out the decimal separator
[currentString replaceOccurrencesOfString:self.decimalSeparator withString:#"" options:NSLiteralSearch range:NSMakeRange(0, [currentString length])];
// Generate a new string for the text input
int currentValue = [currentString intValue];
NSString* format = [NSString stringWithFormat:#"%%.%df", self.maximumFractionDigits];
double minorUnitsPerMajor = pow(10, self.maximumFractionDigits);
NSString* newString = [NSString stringWithFormat:format, currentValue / minorUnitsPerMajor];
textField.text = newString;
// if the cursor was not at the end of the string being entered, restore cursor position
if (cursorOffset != currentLength) {
int lengthDelta = newString.length - currentLength;
int newCursorOffset = MAX(0, MIN(newString.length, cursorOffset + lengthDelta));
UITextPosition* newPosition = [textField positionFromPosition:textField.beginningOfDocument offset:newCursorOffset];
UITextRange* newRange = [textField textRangeFromPosition:newPosition toPosition:newPosition];
[textField setSelectedTextRange:newRange];
}
return NO;
}
You can use below delegate method. Change as your requirements.
-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string{
NSString *replacedSting = [textField.text stringByReplacingCharactersInRange:range withString:string];
NSArray *noOfDigits = [replacedSting componentsSeparatedByString:#"."];
if([noOfDigits count]>=2)
{
NSString *sepStr=[NSString stringWithFormat:#"%#",[noOfDigits objectAtIndex:1]];
return !([sepStr length]>1);
}
return YES;
}
If you're looking to set the tens digit BEFORE the ones digit, this is a bit of a hack but I think it could work. You could use something like the UITextField's delegate method
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
Make sure you set the text field's delegate, and then use this method. You could keep a counter yourself to figure out which index to change upon every keystroke. You would ignore the range and change it out yourself with the one you're keeping track of. For instance, start at index 0 and increment it every time the method is called, but after X seconds of no button being pressed, reset to the first index.
Otherwise, Ramu's answer should work for you!
This question already has answers here:
Limiting text field entry to only one decimal point
(10 answers)
Closed 9 years ago.
I have tried with this code as follow
this helps me to allow user to enter only numbers and dot (decimal point)
But the problem is user can allow n number of decimals in this method.
I want to allow only one decimal
and only two digits after the decima
like 123.00 , 123423432353.99
but not like 123.4.4 , 123.12345, 123...23
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string{
if (string.length == 0) {
return YES;
}
NSCharacterSet *myCharSet = [NSCharacterSet characterSetWithCharactersInString:#"0123456789."];
for (int i = 0; i < [string length]; i++) {
unichar c = [string characterAtIndex:i];
if ([myCharSet characterIsMember:c]) {
return YES;
}
}
UIAlertView *av = [[UIAlertView alloc] initWithTitle:nil message:#"Invalid input" delegate:self cancelButtonTitle:#"Dismiss" otherButtonTitles:nil];
[av show];
return NO;
}
How to allow user to enter only one decimal the text field that too allow only two digits after the decimal
thanks in advance
Best practices Use RegularExpressions whenever you have to perform any string format validation like Email,Phone Number,Currency etc.
This surely will solve your problem. Here sample code below:
First create instance of NSRegularExpression
NSError error;
NSRegularExpression * regExp = [[NSRegularExpression alloc]initWithPattern:#"^\\d{0,10}(([.]\\d{1,2})|([.]))?$" options:NSRegularExpressionCaseInsensitive error:&error];
then use in your relevant method:
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
NSString * existingText = textField.text;
NSString * completeText = [existingText stringByAppendingFormat:#"%#",string];
if ([regExp numberOfMatchesInString:completeText options:0 range:NSMakeRange(0, [completeText length])])
{
if ([completeText isEqualToString:#"."])
[textField insertText:#"0"];
return YES;
}
else
return NO;
}
Use and let me know if it works.
Please try to use this one...It may helps you and please implement your functionality.This code for only 2 digit after "."
NSString *newString = [textField.text stringByReplacingCharactersInRange:range withString:string];
NSArray *sep = [newString componentsSeparatedByString:#"."];
if([sep count]>=2)
{
NSString *sepStr=[NSString stringWithFormat:#"%#",[sep objectAtIndex:1]];
return !([sepStr length]>2);
}
return YES;
I'm having 4 textfields in my application
1.username
2.Email
3.Age
4.Password
User names are 3-25 characters and contain only the characters [a-z0-9]
Age must be between 1-100 inclusive.
Passwords are between 4-12 characters and use only the characters [a-zA-Z0-9]
how can i restrict the textfield with above requirements
please anyone help me out to do this..
Thank you for your effort and consideration.
You can use the methods in the UITextFieldDelegate protocol to validate your fields' content.
More concretely, either you use:
– textFieldShouldEndEditing:
- textFieldShouldReturn:
or you can use:
- textField:shouldChangeCharactersInRange:replacementString:
In the first case, you only validate when the user ends editing the text field; in the second case, you can do the validation at each keystroke.
In all of those methods, you receive an argument textField which you can access like this:
NSString* text = textField.text;
NSUInterger length = [text length];
if (length.....) {
// -- show alert or whatever
return NO;
}
You can validate numbers as the user type by implementing -[UITextField textField:shouldChangeCharactersInRange:replacementString:] method. Do note that this method is called before the change is made, so you need to construct the text that could be the result of the users actions yourself. For example:
-(BOOL)textField:(UITextField*)textField: shouldChangeCharactersInRange:(NSRange*)range
replacementString:(NSString*)string;
{
NSString* text = [textField.text stringByReplacingCharactersInRange:range
withString:string];
// text is now the potential string you should check against.
}
What you do from there is up to your own. Some examples could be:
// Too short?
if ([text length] < 4) ...
// Invalid character?
NSCharacterSet* invalidChars = [[NSCharacterSet alphanumericCharacterSet] invertedSet];
if ([text rangeOfCharacterInSet:invalidChars].location != NSNotFound) ...
For more complex number validation I would use NSNumberFormatter, that has support for validating ranges and more.
You can use UITextFieldDelegate to get done what you want. Assign different values to textfield.tag for each field in - (void)viewDidLoad method and match those tag values to find the relevant field in the (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string.
#define USERNAME_FIELD_TAG 1
#define PASSWORD_FIELD_TAG 2
#define EMAIL_FIELD_TAG 3
#define AGE_FIELD_TAG 4
#pragma mark - UITextFieldDelegate
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
if (textField.tab == USERNAME_FIELD_TAG)
{
if([[NSPredicate predicateWithFormat:#"SELF MATCHES[cd] %#", #"[a-z0-9]{3,35}"] evaluateWithObject:string] == FALSE)
{
textField.text = [textField.text stringByReplacingOccurrencesOfString:string withString:#"" options:NSCaseInsensitiveSearch range:range];
[self selectTextForInput:textField atRange:range];
return NO;
}
}
else if (textField.tab == PASSWORD_FIELD_TAG)
{
if([[NSPredicate predicateWithFormat:#"SELF MATCHES[cd] %#", #"[a-zA-Z0-9]{4,12}"] evaluateWithObject:string] == FALSE)
{
textField.text = [textField.text stringByReplacingOccurrencesOfString:string withString:#"" options:NSCaseInsensitiveSearch range:range];
[self selectTextForInput:textField atRange:range];
return NO;
}
}
else if (textField.tab == EMAIL_FIELD_TAG)
{
if([[NSPredicate predicateWithFormat:#"SELF MATCHES[cd] %#", #"[A-Z0-9a-z._%+-]+#[A-Za-z0-9.-]+\\.[A-Za-z]{2,4}"] evaluateWithObject:string] == FALSE)
{
textField.text = [textField.text stringByReplacingOccurrencesOfString:string withString:#"" options:NSCaseInsensitiveSearch range:range];
[self selectTextForInput:textField atRange:range];
return NO;
}
}
else if (textField.tab == AGE_FIELD_TAG)
{
if([[NSPredicate predicateWithFormat:#"SELF MATCHES[cd] %#", #"[1-100]"] evaluateWithObject:string] == FALSE)
{
textField.text = [textField.text stringByReplacingOccurrencesOfString:string withString:#"" options:NSCaseInsensitiveSearch range:range];
[self selectTextForInput:textField atRange:range];
return NO;
}
}
return YES;
}
// place the cursor at given possition
-(void)selectTextForInput:(UITextField *)input atRange:(NSRange)range {
UITextPosition *start = [input positionFromPosition:[input beginningOfDocument]
offset:range.location];
UITextPosition *end = [input positionFromPosition:start
offset:range.length];
[input setSelectedTextRange:[input textRangeFromPosition:start toPosition:end]];
}
I am creating a text field.
Whenever the value in text field is greater than 100 I need to display an alert saying "value must be less than 100", then the text field's value should be cleared.
How can I do this?
You can have your view controller conform to the UITextFieldDelegate protocol and implement the -textField:shouldChangeCharactersInRange:replacementString: method:
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
if (range.location == 0 && string.length == 0) {
return YES;
}
// Build up the resulting string…
NSMutableString *fullString = [[NSMutableString alloc] init];
[fullString appendString:[textField.text substringWithRange:NSMakeRange(0, range.location)]];
[fullString appendString:string];
// Set up number formatter…
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
NSNumber *replaceNumber = [formatter numberFromString:fullString];
[fullString release];
[formatter release];
return !(replaceNumber == nil || [replaceNumber intValue] > 100);
}
To build the full string you must take into account edits in the middle of the text field too. Made some changes to the code posted above.
-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
BOOL result = NO;
// Build the final string...
NSMutableString *fullString = [[NSMutableString alloc] init];
NSUInteger firstChunkLength = range.location;
NSUInteger secondChunkStart = range.location + range.length;
NSUInteger secondChunkLength = [textField.text length] - secondChunkStart;
[fullString appendString:[textField.text substringWithRange:NSMakeRange( 0, firstChunkLength )]];
[fullString appendString:string];
[fullString appendString:[textField.text substringWithRange:NSMakeRange( secondChunkStart, secondChunkLength )]];
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
NSNumber *replaceNumber = [formatter numberFromString:fullString];
NSNumber *originalNumber = [formatter numberFromString:textField.text];
result = ( replaceNumber != nil && replaceNumber != originalNumber );
[fullString release];
[formatter release];
return result;
}
This method is used to validate a UItextfield for only numeric values.
It is a perfect integer validation.
- (BOOL) textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
NSCharacterSet *nonNumberSet = [[NSCharacterSet characterSetWithCharactersInString:#"0123456789."] invertedSet];
return ([string stringByTrimmingCharactersInSet:nonNumberSet].length > 0);
}
Thankfully, now that we have Swift, this is MUCH easier. You do have to construct the prospective string (hopefully when Apple rewrites Cocoa in Swift this interface will add that).
Meantime, here is a simple one liner to do so:
let constructedString = (self.inputField.text! as NSString).stringByReplacingCharactersInRange(range, withString: string)
Note, self.inputField is of course an outlet to the field in question. Also, the casting to NSString is because the range we are passed is an NSRange.
I have create a class JSInputField (subclass of UItextField) which lets user add validation very easily. For eg.
JSInputField *inputField = [[JSInputField alloc] initWithFrame:CGRectMake(10, 100, 300, 50)];
[self.view addSubview:inputField];
[inputField setPlaceholder:#"Enter Text"];
[inputField setRoundedCorners:UIRectCornerAllCorners];
[inputField addValidationRule:JSCreateRuleNotNullValue]; //This will validate field for null value. It will show error if field is empty.
[inputField addValidationRule:JSCreateRuleNumeric(2)]; //This will validate field for numeric values and restrict to enter value upto 2 decimal places.