NSString's method doubleValue gives unexpected results - iphone

I have a need to convert NSString to double. If this string is in essence integer, then result is OK. If string is decimal, perhaps also with group separators then results are false. Any king of separator (whether "." or ",") whis is first in the string is always used as decimal separator.
I have tried to do something with NSScanner but I simply do not understand how to fix that problem.
Idea is following: whether I put in textfield integer, or decimal with or without group separator, I want to get proper decimal number.
I would be extremely glad to get any help.
Thanks in advance!

[string doubleValue] and a regular NSScanner always uses the . as the decimal separator. A localized NSScanner uses the decimal separator from the current locale. But both don’t know anything about grouping characters so they are inappropriate.
You have to use NSNumberFormatter to do this. Best to set it up in interface builder as #Gobra said. But you can also do this in code like this:
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
[formatter setNumberStyle: NSNumberFormatterDecimalStyle];
double value = [[formatter numberFromString: string] doubleValue];
If you need to know wether the string was valid or not you can check if the NSNumber object returned by numberFromString: is nil before you send it the doubleValue message.

Since you have mentioned text field I assume the problem is in binding some numeric value to the NSTextField. The proper way here is to use NSValueFormatter, there is a customizable formatter for numerics in the IB palette. Just drop it onto the text filed and set up the rules.
Even if the task have nothing to do with UI, you can still use [NSValueTransformer valueTransformerForName:aName] to create a transformed and convert the value with it.

Related

Correct way of getting a currency value from a text field into a NSDecimalNumber

I'm trying to optimise my code a little and I was looking at this part, where I save input prices into my Core Data entity, and I just feel like it's wrong/inefficient.
NSNumberFormatter *priceFormatter = [[NSNumberFormatter alloc] init];
[priceFormatter setNumberStyle:NSNumberFormatterDecimalStyle];
myEntity.price = (NSDecimalNumber *)[priceFormatter numberFromString:self.priceField.text];
Effectively what I think I'm doing here is creating an NSNumberFormatter which has a decimal style (obviously needed for prices, the text field may have an input of 2 instead of 2.00 so it is necessary to some degree) then using that NSNumberFormatter to put the value from the text field into the NSDecimalNumber via a cast. The reason why I want to get 2 into the form 2.00 is to save me from doing it when I load the entity and displaying it in the app. Also the text field regulates the text field so that it can't be more than 2 decimal places etc, so we don't have to worry about that bit.
Is this code efficient? Is there a better way of doing this? Is this even necessary?
Assuming this is a US Dollar only app, you can just use "cents" as your currency. This removes a raft of problems that occur with floating point numbers. You would then have a simple conversion function or method that is used by ALL software displaying values - it divides the value by 100 to get dollars, then to get cents you subtract dollars*100 from the total and what left is cents, that you can easily display:
NSInteger total = ...;
NSInteger dollars = total/100;
NSUInteger cents = abs(total) - abs(dollars*100);
NSString stringWithFormat:#"%%d.%0u", dollars, cents);

CFURLCreateStringByAddingPercentEscapes, strange behavior?

I am trying to encode a URL, I've never done this before, so I'm confused when not getting the results expected.
I'm using CFURLCreateStringByAddingPercentEscapes to do this, but whats returning looks nothing like any online URL encoders/decoders e.g.
-(void)urlEncodedString{
NSString *str = #"\"Hi!! my name is John. \n What's your's?\"";
NSLog([(NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault,(CFStringRef)str, NULL, CFSTR("!$&'()*+,-./:;=?#_~"), kCFStringEncodingUTF8) autorelease]);
}
I was expecting something like:
%5C%22Hi%21%21%20my%20name%20is%20John.%20%5Cn%20What%27s%20your%27s%3F%5C%22
But instead I'm getting:
2i2212yame 0s2ohn3.786691E-27020A2hat º»åå2our 0.0000002
That can't be normal. I've been searching and tried everything, the way I did it apparently should work.
Can anyone point me in the right direction?
You are passing the result as the first string to NSLog which expects a string with formatting which uses the percent signs. You are essentially filling the string with random data in memory in place of each % escape. To fix this log using an Objective-C object specifier:
NSLog(#"%#", [(NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault,(CFStringRef)str, NULL, CFSTR("!$&'()*+,-./:;=?#_~"), kCFStringEncodingUTF8) autorelease]);

dateFromString returns nil on iOS 4.2, works fine on 3.2!

I am converting a string returned from server to NSDate object for further use.
Here is the sample string from server: 2011-01-14T16:05:48.555+05:00
And, I use [dateFormatter setDateFormat:#"yyyy-MM-dd'T'HH:mm:ss"];
I am able to successfully convert it to an NSDate object using dataFromString method on iOS 3.2 (device & sim both).
However, the same code returns nil on iOS 4.2 on device & sim both.
I have also tried many variations of date format patterns such as:
[dateFormatter setDateFormat:#"yyyy-MM-dd'T'HH:mm:ss.SSSZ"];
[dateFormatter setDateFormat:#"yyyy-MM-dd'T'HH:mm:ss.SSSZZZZ"];
[dateFormatter setDateFormat:#"YYY-MM-DD'T'HH:mm:ss.fffK"];
but nothing works!!
Am I missing something here?
EDIT: I tried to hard-code the input string and after trying many variations, the following string 2011-01-14T16:05:48.555+0500 worked with "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
Notice that the only difference in this string and the one from server is; the colon (:) in TimeZone value. I tried to find a date pattern where I can mention the colon in timezone with no success.
Any clue??
I don't know the right answer on your question, but you may try this solution:
You can manually delete colon(:) in timeZone value in the string returned by server and write this string to new, and then use "yyyy-MM-dd'T'HH:mm:ss.SSSZ".
Source code:
NSString *stringFromServer=#"2011-01-14T16:05:48.555+05:00";
NSString *str1 = [stringFromServer substringToIndex:26];
NSString *str2 = [stringFromServer substringFromIndex:27];
NSString *result=[NSString stringWithFormat:#"%#%#",str1,str2];
so if this works:
EDIT: I tried to hard-code the input string and after trying many variations, the following string 2011-01-14T16:05:48.555+0500 worked with "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
Blockquote
then string "result" will work with "yyyy-MM-dd'T'HH:mm:ss.SSSZ"

Determining the number of decimal numbers for a currency

On the iPhone:
Using the US locale, a currency looks like this: $1,234.56
Using the UK locale, a currency looks like this: £1,234.56
Using the German (Germany) locale, a currency looks like this: 1.234,56 €
and finally, a Japanese currency: ¥1,234
The Japanese currency has no decimals and this impacts my custom keyboard significantly. I'm trying to find a method in the Cocoa-touch framework which will tell me how many decimal places a specific currency has - my hard-coded value of 2 isn't doing me justice :(
Can anyone help?
You should be able to use the CFNumberFormatter objects to get the information you need. Specifically you could use CFNumberFormatterGetDecimalInfoForCurrencyCode:
CFStringRef localeIdent = CFSTR("JPY");
int numDecimals;
double rounding;
BOOL result = CFNumberFormatterGetDecimalInfoForCurrencyCode(localeIdent, &numDecimals, &rounding);
I hope that helps.
I haven't programmed in Cocoa for ages, but from the documentation for NSNumberFormatter, there's a function called 'currencyDecimalSeparator' - that might at least tell you if a currency has one at all, which might be a start?
Financial companies maintain databases of this kind of information. You might be able to buy the data or import it from an online source.
Note also: some currencies need three or four decimal places. See http://www.londonfx.co.uk/ccylist.html for examples.
I had a similar problem but the answers above didn't really solve my problem.
I ended up using the maximumFractionDigits method on NSNumberFormatter which gave me 0 for Japanese locale. Make sure you use a NSNumberFormatterCurrencyStyle for the formatter, otherwise you'll see decimal places in other formatters.
NSNumberFormatter *currencyFormatter = [[NSNumberFormatter alloc] init];
[currencyFormatter setLocale:[NSLocale currentLocale]];
[currencyFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
NSLog(#"Number of decimal places %i", [currencyFormatter maximumFractionDigits]);

NSNumberFormatter to format US Telephone Numbers

I'm trying to convert a string of numbers, entered by the user, into a sexy string like Phone.app on the iPhone. Here is the code I'm using, which doesn't work (no special format comes out) and after a certain number of digits it just starts adding "0" to the end of the string.
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
[formatter setNumberStyle:NSNumberFormatterNoStyle];
[formatter setPositiveFormat:#"+# (###) ###-###"];
[formatter setLenient:YES];
NSString *strDigits = [self stringByReplacingOccurrencesOfRegex:#"[^0-9+]" withString:#""];
return [formatter stringFromNumber:[NSNumber numberWithDouble:[strDigits doubleValue]]];
I think your issue is that NSNumberFormatter does not support brackets, spaces or dashes. I tried to implement the same method as you and it failed silently and just output unformatted text.
The general problem here is that the iPhone SDK doesn't provide a method to format phone numbers in a locale dependent way.
I have raised bugs with Apple for the following (two of these were duplicates of known issues so I've included Apple's original bug # for those):
#6933244 - Need iPhone SDK interface to format text as locale dependent phone number
#5847381 - UIControl does not support a setFormatter method
#6024985 - Overridden UITextField drawTextInRect method is never called
In an ideal world Apple would provide an NSPhoneNumberFormatter, and you would call setFormatter on your UIControl so it displayed text in a nice pretty way. Unfortunately that doesn't exist on the iPhone.
The UIPhoneFormats.plist contains predefined phone formats for each locale. So, if you're only interested in US phone numbers, you'll need to consider these formats:
+1 (###) ###-####
1 (###) ###-####
011 $
###-####
(###) ###-####
I had to do something similar, and I shared the results I got here:
http://the-lost-beauty.blogspot.com/2010/01/locale-sensitive-phone-number.html
Well a phone number should be 10 characters(11 with the leading 1), so you should start by changing this:
[formatter setPositiveFormat:#"+# (###) ###-####"];
And speaking of the leading 1, you need to check for that too.
Apple says you can implement your custom formatter if existing formatters' functionality is not enough:
NSFormatter is intended for subclassing. A custom formatter can restrict the input and enhance the display of data in novel ways. For example, you could have a custom formatter that ensures that serial numbers entered by a user conform to predefined formats. Before you decide to create a custom formatter, make sure that you cannot configure the public subclasses NSDateFormatter and NSNumberFormatter to satisfy your requirements.
For instructions on how to create your own custom formatter, see Creating a Custom Formatter.
See the section in Data Formatting Guide
One thing that is never mentioned here is that a NSNumberFormatter will always convert the value into, well, a NSNumber.
If you want a more generalized NSFormatter, subclass NSFormatter and implement the methods mentioned in the Creating a Custom Formatter.