NSNumberFormatter not allowing decimal input - objective-c

If I enter a decimal point '.' into my UITextField, the number formatter called does not recognise the decimal point and continues as if the decimal point has not been entered. I.e If I entered 200.9, the decimal point would not show up in the textfield and the text of the textfield would be 2009.
I want to limit the number of digits after the decimal point to 2 as I believe I am doing below. Please can you tell me what I am doing to cause this?
- (BOOL) textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
BOOL isDeleting = [textField.text substringWithRange:range].length > string.length;
int index = [textFields indexOfObject:textField];
NSString *input;
if (isDeleting == NO)
input = [textField.text stringByAppendingString:string];
else {
NSMutableString *str = [textField.text mutableCopy];
[str deleteCharactersInRange:range];
input = [[str copy] autorelease];
[str release];
}
if ([input isEqualToString:#"£"] || ([input isEqualToString:#""] && index != 1)) {
[textField setText:#"£"];
}
else {
if (index != 1)
[textField setText:[self numberFormattedString:input]];
else
[textField setText:input];
}
return NO;
}
- (NSString *) numberFormattedString:(NSString *)str {
str = [str stringByReplacingOccurrencesOfString:#"£" withString:#""];
str = [str stringByReplacingOccurrencesOfString:#"," withString:#""];
NSNumberFormatter *formatter = [[[NSNumberFormatter alloc] init] autorelease];
[formatter setNumberStyle:NSNumberFormatterCurrencyStyle];
NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:#"en-UK"];
[formatter setLocale:locale];
[locale release];
[formatter setAllowsFloats:YES];
[formatter setMaximumFractionDigits:3];
[formatter setMinimumFractionDigits:0];
[formatter setDecimalSeparator:#"."];
return [formatter stringFromNumber:[NSNumber numberWithFloat:[str floatValue]]];
}
TIA.

Let's say you enter the number 100 and then a decimal point. When numberFormattedString is called with the string £100., it's going to get rid of the £, and str will contain the string 100. which [str floatValue] converts to the float value 100, and finally your number formatter spits it back out as the string £100 without any decimal point. So basically [str floatValue] is killing your decimal point.
One solution is to check for when you get the decimal point as your replacement string in the text field delegate method, and skip calling numberFormattedString in that case. Then when the user enters the next digit, you can carry on calling numberFormattedString, and the conversion should happen correctly. You'll just have to make sure that the user can only enter one decimal point.
EDIT: I just realized that my suggested solution still won't work if you enter a 0 after the decimal point (e.g. 100.0), but I'm sure there is a minor tweak that you can figure out to solve that.

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
NSCharacterSet *numSet = [NSCharacterSet characterSetWithCharactersInString:#"0123456789."];
NSString *newString = [textField.text stringByReplacingCharactersInRange:range withString:string];
int charCount = [newString length];
if ([newString rangeOfCharacterFromSet:[numSet invertedSet]].location != NSNotFound
|| [string rangeOfString:#"."].location != NSNotFound
|| charCount > 15) {
return NO;
}
// if (charCount == 4 || charCount == 9 || charCount==13) {
// newString = [newString stringByAppendingString:#"."];
// }
NSLog(#"IN method");
textField.text = newString;
return NO;
}

Related

How to format UITextField with NSNumberFormatter?

Hope this helps others, since I haven't seen any similar post on the web that shows how to format a text field using NSNumberFormatter but at the same time keep the UITextField cursor position to where it should naturally be. Those, because after formatting, the NSString from inside the UITextField and setting it back to the field you end up with the cursor placed an the end of the field.
Also it will be nice to convert it to Swift for those that needs it.
And here is my answer to the issue, I am using a UIKeyboardTypeNumberPad, but also it will work fine with a UIKeyboardTypeDecimalPad, if other keyboard types are used, feel free to add a regex before using the next code:
- (int)getCharOccurencies:(NSString *)character inString:(NSString *)string{
NSMutableArray *characters = [[NSMutableArray alloc] initWithCapacity:[string length]];
for (int i=0; i < [string length]; i++) {
NSString *ichar = [NSString stringWithFormat:#"%c", [string characterAtIndex:i]];
[characters addObject:ichar];
}
int count = 0;
for (NSString *ichar in characters) {
if ([ichar isEqualToString:character]) {
count++;
}
}
return count;
}
- (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]];
}
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string{
NSString *charUsedToFormat = #",";
NSMutableString *textString = [NSMutableString stringWithString:[textField text]];
[textField setTintColor:[UIColor darkGrayColor]];
if ([string isEqualToString:charUsedToFormat]) {
return NO;
}
if (range.location == 0 && [string isEqualToString:#"0"]) {
return NO;
}
if ([[textString stringByReplacingCharactersInRange:range withString:string] length] == 0) {
textField.text = #"";
[self selectTextForInput:textField atRange:NSMakeRange(0, 0)];
return NO;
}
NSString *replacebleString = [textString substringWithRange:range];
if (string.length == 0 && [replacebleString isEqualToString:charUsedToFormat]) {
NSRange newRange = NSMakeRange( range.location - 1, range.length);
range = newRange;
textString = [NSMutableString stringWithString:[textString stringByReplacingCharactersInRange:newRange withString:string]];
}else{
textString = [NSMutableString stringWithString:[textString stringByReplacingCharactersInRange:range withString:string]];
}
int commmaCountBefore = [self getCharOccurencies:charUsedToFormat inString:textString];
textString = [NSMutableString stringWithString:[textString stringByReplacingOccurrencesOfString:charUsedToFormat withString:#""]];
NSNumber *firstNumber = [NSNumber numberWithDouble:[textString doubleValue]];
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
[formatter setNumberStyle:NSNumberFormatterDecimalStyle];
//just in case
[formatter setFormatterBehavior:NSNumberFormatterBehavior10_4];
[formatter setGroupingSeparator:charUsedToFormat];
[formatter setDecimalSeparator:#""];
[formatter setMaximumFractionDigits:0];
textString = [NSMutableString stringWithString:[formatter stringForObjectValue:firstNumber]];
textField.text = textString;
int commmaCountAfter = [self getCharOccurencies:charUsedToFormat inString:textString];
int commaDif = commmaCountAfter - commmaCountBefore;
int cursorPossition = (int)range.location + (int)string.length + commaDif;
//set cursor position
NSLog(#"cursorPossition: %d", cursorPossition);
[self selectTextForInput:textField atRange:NSMakeRange(cursorPossition, 0)];
return NO;
}

NSNumberFormatter unable to allow numbers to start with a 0

So I'm attempting to automatically add slashes between 2 digits when a user enters in their birthday, but for some reason when the birthday starts with a 0, the number formatter erases it and messes up the birthday. I've got my code below, could someone help me figure out how to do this? Thanks in advance!
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init] ;
[formatter setGroupingSeparator:#"/"];
[formatter setGroupingSize:2];
[formatter setUsesGroupingSeparator:YES];
[formatter setSecondaryGroupingSize:2];
NSString *num = textField.text ;
if(![num isEqualToString:#""])
{
num= [num stringByReplacingOccurrencesOfString:#"/" withString:#""];
NSString *str = [formatter stringFromNumber:[NSNumber numberWithDouble:[num doubleValue]]];
textField.text=str;
}
What you could do is the following:
Check the length of the string
If length mod 2 == 0 then add "/"
Log your string
I'm not saying this is recommended but it might help you a bit!
- (void)controlTextDidChange:(NSNotification *)obj{
NSString *num = [textField stringValue] ;
if (num.length%2==0)
{
NSString *someText = [NSString stringWithFormat: #"%#/ ", num];
num = someText;
}
textField.stringValue = num;
}
Something like this may help:
NSMutableString *string = #"YOUR TEXTFIELD TEXT";
NSString *lastString = [string substringWithRange:NSMakeRange(string.length-2, 1)];
if ([lastString isEqualToString:#"/"]) {
return;
}
if (string.length == 2 || string.length == 5) {
[string appendString:#"/"];
}

How to put comma and decimals in my UITextField dynamically?

I want to add a ',' in '11000' like this '11,000' and decimal ('.') in 465 like 4.65.
I wrote this for Comma :
- (BOOL) textField: (UITextField *)textField shouldChangeCharactersInRange: (NSRange)range replacementString: (NSString *)string {
NSString *unformattedValue;
NSNumberFormatter *formatter;
NSNumber *amount;
switch ([textField tag]) {
case 102:
unformattedValue = [textField.text stringByReplacingOccurrencesOfString:#"," withString:#""];
unformattedValue = [unformattedValue stringByReplacingOccurrencesOfString:#"." withString:#""];
formatter = [[NSNumberFormatter alloc] init];
[formatter setNumberStyle:NSNumberFormatterDecimalStyle];
[formatter setGroupingSeparator:#","];
[formatter setDecimalSeparator:#"."];
amount = [NSNumber numberWithInteger:[unformattedValue intValue]];
textField.text = [formatter stringFromNumber:amount];
break;
default:
break;
}
return YES;
}
And what this doing is it actually putting the comma like this 1,1000 for 11000. And i am not able to do anything close for decimal.
Please help!!
NSNumberFormatter can handle this conversion from string to number and back for you. No need to strip characters yourself with stringByReplacingOccurrencesOfString or use less lenient string to numeric conversion methods like intValue (and at the very least don't use intValue if you want to be able to get a decimal).
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
[formatter setNumberStyle:NSNumberFormatterDecimalStyle];
NSNumber *amount = [formatter numberFromString:textField.text];
textField.text = [formatter stringFromNumber:amount];
Depending on the input you need to tolerate you might still want to do some other cleanup of the input string if NSNumberFormatter's lenient setting is not enough. You could also use multiple number formatters if you wanted to parse an input string in one format and then output it in another.
Use the following code to manually add commas at the right locations. You can get the logic from this code and tweak it to suit your requirement.
-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
NSLog(#"Text:%#textField length:%dRange.length:%lu , Range.location:%lu :: replacementString:%#",textField.text,textField.text.length,(unsigned long)range.length,(unsigned long)range.location,string);
NSMutableString *tempString=textField.text.mutableCopy;
int digitsCount;
if ([string isEqualToString:#""]) //digit removed
{
NSLog(#"digit removed, string length after trimming:%d",[tempString stringByReplacingOccurrencesOfString:#"," withString:#""].length);
digitsCount=[tempString stringByReplacingOccurrencesOfString:#"," withString:#""].length-1; //digit removed
}else ///digit added
{
digitsCount=[tempString stringByReplacingOccurrencesOfString:#"," withString:#""].length+1 ;
}
NSLog(#"Number of digits:%d",digitsCount);
switch (digitsCount)
{
//case 1:textField.text=[tempString stringByReplacingOccurrencesOfString:#"," withString:#""];
//break;
case 3:textField.text=[tempString stringByReplacingOccurrencesOfString:#"," withString:#""];
break;
case 4:
//remove previous comma...
tempString=[tempString stringByReplacingOccurrencesOfString:#"," withString:#""].mutableCopy;
[tempString insertString:#"," atIndex:1];
textField.text=tempString;
break;
case 5:
//remove previous comma...
tempString=[tempString stringByReplacingOccurrencesOfString:#"," withString:#""].mutableCopy;
[tempString insertString:#"," atIndex:2];
textField.text=tempString;
break;
case 6:
//remove previous comma...
tempString=[tempString stringByReplacingOccurrencesOfString:#"," withString:#""].mutableCopy;
[tempString insertString:#"," atIndex:1];
[tempString insertString:#"," atIndex:4];
textField.text=tempString;
break;
default:
break;
}
return YES;
}

Customizing UITextField for Currency format works well with iOS 4.x but in iOS 5 gives null

I am customizing UItextField for local currency symbol and comma, using following link :
http://www.thepensiveprogrammer.com/2010/03/customizing-uitextfield-formatting-for.html
NSNumber *actualNumber = [currencyFormatter numberFromString:[mstring
stringByReplacingOccurrencesOfString:localeSeparator withString:#""]];
In iOS 5 this actual number is always null and in iOS 4.x it is working fine
My code's main method for this purpose is :
-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
if (textField.tag == 1)
{
if(true)
{
NSMutableString* mstring = [[textField text] mutableCopy];
if([mstring length] == 0)
{
//special case...nothing in the field yet, so set a currency symbol first
[mstring appendString:[[NSLocale currentLocale] objectForKey:NSLocaleCurrencySymbol]];
//now append the replacement string
[mstring appendString:string];
}
else
{
//adding a char or deleting?
if([string length] > 0)
{
[mstring insertString:string atIndex:range.location];
}
else
{
//delete case - the length of replacement string is zero for a delete
[mstring deleteCharactersInRange:range];
}
}
NSString* localeSeparator = [[NSLocale currentLocale]
objectForKey:NSLocaleGroupingSeparator];
NSNumber *actualNumber = [currencyFormatter numberFromString:[mstring
stringByReplacingOccurrencesOfString:localeSeparator
withString:#""]];
NSLog(#"%#",actualNumber);
[textField setText:[currencyFormatter stringFromNumber:actualNumber]];
[mstring release];
}
//always return no since we are manually changing the text field
return NO;
}
else
{
return YES;
}
}
and This is the initialization
NSLocale *paklocal = [[[NSLocale alloc] initWithLocaleIdentifier:#"en_PAK"] autorelease];
currencyFormatter = [[NSNumberFormatter alloc] init];
[currencyFormatter setFormatterBehavior: NSNumberFormatterBehavior10_4];
[currencyFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
[currencyFormatter setMaximumFractionDigits:0];
[currencyFormatter setLocale:paklocal];
NSMutableCharacterSet *numberSet = [[NSCharacterSet decimalDigitCharacterSet] mutableCopy];
[numberSet formUnionWithCharacterSet:[NSCharacterSet whitespaceCharacterSet]];
nonNumberSet = [[numberSet invertedSet] retain];
[numberSet release];
I think you're having a problem because textField:shouldChangeCharactersInRange:replacementString: adds the currency symbol for [NSLocale currentLocale], which may be different from the locale used by currencyFormatter. In the simulator on my computer, it added $ (dollar) signs, to mstring, which were logically enough rejected by currencyFormatter.
When you construct paklocal, store it along with currencyFormatter and use it instead of [NSLocale currentLocale].
If you have further trouble with currencyFormatter, use NSLog to display the string you send into it.

How to enter Numbers only in UITextField and limit maximum length?

In UITextField we Enter Numeric only and limit up to 3 numeric for this i used below code
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
NSUInteger newLength = [textField.text length] + [string length] - range.length;
NSCharacterSet *cs = [[NSCharacterSet characterSetWithCharactersInString:NUMBERS_ONLY] invertedSet];
NSString *filtered = [[string componentsSeparatedByCharactersInSet:cs] componentsJoinedByString:#""];
while (newLength < CHARACTER_LIMIT) {
return [string isEqualToString:filtered];
}
/* Limits the no of characters to be enter in text field */
return (newLength > CHARACTER_LIMIT ) ? NO : YES;
}
When i press long press on textfield (like below image )and enter string between two numbers it's allowing all special characters and charterers also.
Not that I don't like the answer I wrote at this question, that was copy & pasted here also. I'll try to explain your error.
This answer is based on the assumption that your constants are defined something like this:
#define NUMBERS_ONLY #"1234567890"
#define CHARACTER_LIMIT 3
The reason your logic is failing is that you never handle the event when newLength will be equal to the CHARACTER_LIMIT.
To illustrate suppose your textfield is empty and you request to paste the string #"ABC" to the textfield, your delegate method is called. You create the string filtered which correctly evaluates to an empty string, and you can't wait to execute the line return [string isEqualToString:filtered]; but you never actually evaluate that line because you don't meet the entry requirements for the while loop, because newLength is 3. so the simple return (newLength > CHARACTER_LIMIT ) ? NO : YES; decides the return value.
If your CHARACTER_LIMIT is actually 4 for some reason, just imagine #"ABCD" as the string the logic still applies.
Here is a simple example of your function corrected to work. Again I'm assuming that CHARACTER_LIMIT is equal to 3.
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
NSUInteger newLength = [textField.text length] + [string length] - range.length;
NSCharacterSet *cs = [[NSCharacterSet characterSetWithCharactersInString:NUMBERS_ONLY] invertedSet];
NSString *filtered = [[string componentsSeparatedByCharactersInSet:cs] componentsJoinedByString:#""];
return (([string isEqualToString:filtered])&&(newLength <= CHARACTER_LIMIT));
}
-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string{
// Check for non-numeric characters
NSUInteger lengthOfString = string.length;
for (NSInteger loopIndex = 0; loopIndex < lengthOfString; loopIndex++) {
unichar character = [string characterAtIndex:loopIndex];
if (character < 48) return NO; // 48 unichar for 0
if (character > 57) return NO; // 57 unichar for 9
}
// Check for total length
NSUInteger proposedNewLength = textField.text.length - range.length + string.length;
if (proposedNewLength > 3) return NO;
return YES;
}
Its working fine for me u can use this code
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
NSString *s = [textField.text stringByReplacingCharactersInRange:range withString:string];
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:#"^\\d{0,3}$" options:0 error:nil];
NSTextCheckingResult *match = [regex firstMatchInString:s options:0 range:NSMakeRange(0, [s length])];
return (match != nil);
}
Check the UITextFieldDelegate method
- (BOOL) textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
This method will be invoked each time after you type/tap a character or press cut/paste and before that action changes the characters displayed. Here you can very well to your check for maxLength and numbers-only text input(by using RegularExpression patter as given below) and return YES or NO basd on that. If you return YES it will do the change otherwise it will not. Hope this clears your doubt.
I prefer using an NSNumberFormatter to validate the text can be parsed into a number. NSNumberFormatter returns nil if the string cannot be converted to a number. This lets you react to the input (i.e. make text red, throw up a alert)
NSNumber* value = nil;
NSNumberFormatter* format = [[NSNumberFormatter alloc] init];
value = [format numberFromString:someValueString];//returns and
if (value == nil){
//do something useful i.e. show alert
}