NSNumberFormatter to format US Telephone Numbers - iphone

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.

Related

iOS 6.1 NSDateFormatter has changed since iOS 5 - broken parsing?

Our iOS iPhone app contains this code which produced a valid NSDate object below named resultDate in iOS 5:
static NSDateFormatter *invariantFmt = nil;
if (!invariantFmt) {
invariantFmt = [[NSDateFormatter alloc] init];
NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:#"en_US_POSIX"];
[invariantFmt setLocale:locale];
[locale release];
[invariantFmt setDateStyle:NSDateFormatterShortStyle];
[invariantFmt setTimeStyle:NSDateFormatterMediumStyle];
}
NSDate *resultDate = [invariantFmt dateFromString:#"08/04/2010 10:43:39 AM"];
After upgrading to XCode 4.6 and iOS 6.1, that code now gives a nil for resultDate, so something has changed with what they use to parse. The release notes says nothing about NSDateFormatter changing. Internet research has turned up only that they might have changed to use a newer Unicode UTS Locale parsing standard. Obviously they changed something. After twiddling with code and taking a known valid NSDate object and applying the same NSDateFormatter settings to get a NSString, I find that iOS 6.1 likes this string instead: #"08/04/2010, 10:43:39 AM"
The only difference being that extra comma after the date portion. Using that in iOS 6.1 gives back a valid date with the same above code. Anyone seeing this and understand why that's different or if that's an ok Unicode change or a Apple bug?
The format styles should only be used to convert NSDate objects to text to display to the user. When parsing a date string in a known format, you must use a specific format, never the styles. The use of the en_US_POSIX locale is used to ensure that the format you specify isn't tweaked by the OS based on user preferences such as the 24-hour time setting.
So, as you suspected, you need to remove the two calls to set the date and time styles and replace them with a call to set a specific format that matches your known date/time string you need to parse.
Generally you should not output a formatted Date to a file, and later want to parse it back.
A Date should be stored as long value UTC (with or without additional TimeZone offset).
Only in the last moment, before visualizing a Date to UI it should be formatted and local time applied.
This not only i smy experience, it it is also stated in Apple DateFormatting Doku.

Localized date not translating the days

Here is how I am localising days:
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
NSString *dayFormat = [NSDateFormatter dateFormatFromTemplate:#"EEEE" options:0 locale:[NSLocale currentLocale]];
[dateFormatter setDateFormat:dayFormat];
[dateFormatter setLocale:[NSLocale currentLocale]];
NSString *day = [dateFormatter stringFromDate:date];
And yet it seems to be returning English days of the week (Monday, Tuesday etc) rather than the device language (which has been set to German in the simulator).
Any idea where I'm going wrong?
Update after doing some research on device I've realised that its actually the region setting, not the language setting which changes the date language. Odd, but I guess its done for a reason.
Thanks
The language of the date is set by the region not the language. This has to be a bug. If I'm in Germany, but an English speaker I don't want to have my dates in German, surely?
Anyway, this is why. You have to change language and region.
I've had the same issue. It wouldn't work on the simulator, but it would on a device. Can you try it ? I did not however solve it, I did not even look more into it as it was working perfectly on the device, which is truly the main target of your app.
Edit:
This comes from Apple's doc:
currentLocale
Returns the logical locale for the current user.
+ (id)currentLocale
Return Value
The logical locale for the current user. The locale is formed from the settings for the current user’s chosen system locale overlaid with any custom settings the user has specified in System Preferences.
Discussion
Settings you get from this locale do not change as a user’s
Preferences are changed so that your operations are consistent.
Typically you perform some operations on the returned object and then
allow it to be disposed of. Moreover, since the returned object may be
cached, you do not need to hold on to it indefinitely. Contrast with
autoupdatingCurrentLocale.
Maybe you can try using:
preferredLanguages
Returns the user's language preference order as an array of strings.
+ (NSArray *)preferredLanguages
Return Value
The user's language preference order as an array of NSString objects, each of which is a canonicalized IETF BCP 47 language identifier.

NSString's method doubleValue gives unexpected results

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.

Stripping the 12 Hour clock suffix from an NSString

I have a shortage of screen real estate for my time labels in my iPhone app.
My solution is to have the time e.g. 12:00 on one line and then if the users current locale specifies that an AM-PM is used, have these in a second label below it.
Since AM-PM also have localized variants I can't just look for the letters "AM" or "PM", then I thought about stripping the last two letters, but by checking I found out some languages uses a format like this: "F.M." "E.M". My next thought was to strip everything after the first 5 digits(12:34), but for hour intervals below 10 that is no good either.
Is there a "locale safe" way of always removing the localized suffix and move it to a new string, regardless of the users settings?
Thank you in advance:)
There is no locale safe way of doing that.
Use NSDateFormatter to generate two strings.
NSDateFormatter *timeOfDayFormatter = [[[NSDateFormatter alloc] init] retain];
[timeOfDayFormatter setDateFormat:#"hh:mm"];
NSDateFormatter *amPmFormatter = [[[NSDateFormatter alloc] init] retain];
[amPmFormatter setDateFormat:#"aa"];
NSLog(#"Time is: %# %#",
[timeOfDayFormatter stringFromDate:theDate],
[amPmFormatter stringFromDate:theDate]);
Now you can layout your user interface with the two strings.
The 24-hour format is standard where I live. I'd "force" that format on the user if screen real estate is a real problem.
I ended up solving it like this and then manually test it with 15+ locale settings:
NSArray *timeParts = [NSArray arrayWithArray:[[timeFormatter stringFromDate:myDate] componentsSeparatedByString:#" "]];
Then I test the timeParts array:
if ([timeParts count] > 1) {...}
If the count is 1 it is a locale without the suffix and I don't set the "AM/PM" label.
Else, I set both labels, the timeLable with [timeParts objectAtIndex:0] and the localeLabel with [timeParts objectAtIndex:1]
This seems to be a stable solution for all locales.

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]);