NSNumberFormatter bug on iOS 14 - objective-c

Seems like there is a bug with NSNumberFormatter on iOS14 when your region is set to Cambodia where the amount is being formatted to "1.234,56" which is not the format being used there.
It was okay on iOS13. I cannot see any references in regards to this change.
Is there any workaround for this?
UPDATE:
Issue still exists with iOS 14.2

I think you can modify the parameters for NumberFormatter to suit your needs. Something like this
let formatter = NumberFormatter()
//formatter.locale = Locale.init(identifier: Locale.current.identifier)
formatter.numberStyle = .currency
formatter.currencyGroupingSeparator = ","
formatter.currencyDecimalSeparator = "."
//by default its 3
formatter.maximumFractionDigits = 2
print(formatter.string(from: 1000000.00))
//Outputs Optional("TND 1,000,000.00")

Related

Why does NSNumberFormatter skip a number?

So lets say I have the following:
NSNumber *num = [NSNumber numberWithDouble:12345678901234567];
Now I would like for that to be formatted as a string in the following format: 1.234567E16
I have tried both:
formatter.numberStyle = NSNumberFormatterScientificStyle;
formatter.maximumFractionDigits = 6;
and
formatter.positiveFormat = #"0.00000E0";
But my result is always 1.23457E16
Why does it skip the 6?
If I change it to maximumFractionDigits = 7 it would print 1.234568E16
What am I doing wrong?
It's most likely rounding the 6 up to 7.
I do not suggest using formatter.roundingMode = NSNumberFormatterRoundDown; since that is a hack.
What you need to do to force 6 digits is set the maximum and the minimum number of fractional digits.
[formatter setMaximumFractionDigits:6];
[formatter setMinimumFractionDigits:6];

How do I force a sign-character on the output of an NSNumberFormatter

I want to use a number formatter to generate my output, so the number is automatically formatted for the user's locale, but I want it to work like "%+.1f" does in printf(), that is always have a sign specified.
NSNumberFormatter *nf = [[NSNumberFormatter alloc] init];
nf.numberStyle = NSNumberFormatterDecimalStyle;
nf.maximumFractionDigits = 1;
double val = 3.1234;
label.text = [NSString stringWithFormat: #"XXX %# XXX", [nf stringFromNumber: [NSNumber numberWithDouble: val]]];
I want the label to come out "XXX +3.1 XXX" in the US and the appropriate but equivalent string for any other location. The only things I can find are setPositiveFormat: and setPositivePrefix:.
But I don't want to set the format since I don't know how to format numbers in other countries; I don't know if a plus-sign is used to designate a positive number in Arabic or Russian or some culture I have not thought of. I do know, for example, that decimal points, commas, spaces, etc., all have different meanings in European countries compared to the U.S. - Could the same be true for +/- signs?
What I do currently is:
label.text = [NSString stringWithFormat: #"XXX %s%# XXX", (val < 0) ? "" : "+",
[nf stringFromNumber: [NSNumber numberWithDouble: val]]];
But this presumes that '+' and '-' are correct for all formats.
I'm sure it must be there since it is a standard formatting thing that has been in printf() since the dark ages...
How about this:
NSNumberFormatter *nf = [[NSNumberFormatter alloc] init];
nf.numberStyle = NSNumberFormatterDecimalStyle;
nf.maximumFractionDigits = 1;
double val = 3.1234;
NSString *sign = (val < 0) ? [nf minusSign] : [nf plusSign];
NSString *num = [nf stringFromNumber:#(abs(val))]; // avoid double negative
label.text = [NSString stringWithFormat: #"XXX %#%# XXX", sign, num];
You may need to check to see if num has the sign prefix or not so it isn't shown twice.
Edit: After some playing around, it has been determined, for the "Decimal" style, that no current locale uses a positivePrefix. No current locale uses a plusSign other than the standard + character. No current locale uses a negativePrefix that is different than minusSign. No current locale uses either positiveSuffix or negativeSuffix.
So an easier approach would be to do:
NSNumberFormatter *nf = [[NSNumberFormatter alloc] init];
nf.numberStyle = NSNumberFormatterDecimalStyle;
nf.maximumFractionDigits = 1;
[nf setPositivePrefix:[nf plusSign]];
[nf setNegativePrefix:[nf minusSign]];
label.text = [nf stringFromNumber:#(val)];
This case it's simple, just add the prefix:
nf.positivePrefix= nf.plusSign;
Though it won't use the user's locale, you can do the following to generate the +/- sign without the somewhat expensive overhead of an NSNumberFormatter:
// assume 'number' is an NSNumber
label.text = [NSString stringWithFormat:#"%+.02f", [number floatValue]];
Simple Case:
let f = NumberFormatter()
f.positivePrefix = f.plusSign
Currency Case :
Hack needed, because setting the prefix to plusSign only will remove the currency symbol.
let f = NumberFormatter()
f.numberStyle = .currency
f.positivePrefix = f.plusSign + f.currencySymbol
There is a bit more work depending on the locale.. The currency symbol may be before, or after, but this is probably another subject..
Edit:
Even if it is another subject, I'd say a possible solution to the problem above is to subclass NSNumberFormatter :
override func string(from number: NSNumber) -> String? {
returns ( number.doubleValue >= 0 ? super.plusSign : "" ) + super.string(from: number)
}
This way, NSNumberFormatter should manage the currency position while your subclass simply prepend the + sign. No time to test this in depth, but at least it is an approach.
The underlying formatting language for NSNumberFormatter doesn't have any provision for what you want to do -- it will allow you to specify a localized positive sign on exponents, but not for the entire formatted string. Nor does NSLocale seem to make available the localized positive sign.
Aside from making a dummy string that includes an exponent, pulling the localized positive sign out, and putting your final formatted string together by hand, I think you're out of luck.
A reusable formatter in swift:
var numberFormatter: NSNumberFormatter {
let formatter = NSNumberFormatter()
formatter.numberStyle = .DecimalStyle
formatter.locale = NSLocale(localeIdentifier: "it_IT")//your Locale
formatter.maximumFractionDigits = 2
formatter.minimumFractionDigits = 0
formatter.positivePrefix = formatter.plusSign
return formatter
}
Then use it:
let myDoubleValue = 12.00
let myStringNumber = numberFormatter.stringFromNumber(myDoubleValue)!
I don't think any of the previous answers will actually take into consideration everything you mentioned in your question.
It is true that NumberFormatter does not have an option to set the plus sign visible for all positive numbers when formatting currency values.
Also, replacing prefixes and suffixes will likely break the format for some regions and always replacing a prefix will only work if the set locale uses the currency symbol on the left.
A simple way to address this without losing the locale formatting can be seen below:
var value: Double = 3.1234
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.maximumFractionDigits = 1
if value > 0 {
return formatter.string(for: value.negated())?.replacingOccurrences(
of: formatter.minusSign,
with: formatter.plusSign
)
} else {
return formatter.string(for: value)
}
Even though this can be seen as hack, it's an effective way to achieve everything you mentioned without manually writing a number formatter.

NSLocale currency symbol, show before or after amount value

I am using StoreKit to implement an in app purchase store in my application.
I have a custom design and it means that the value of the price should be white and large, and the currency symbol smaller, darker and aligned to the top of the price value.
I can get the currency symbol without any problems by using the NSLocale in SKproduct's priceLocale property, and the value of the price in the price property.
My problem is knowing when I should put the currency symbol before the price and when to put it after the price.
Examples:
$5,99
0,79€
I could easily use the NSNumberFormatter to get this worked out "out of the box", but since my layout defines a different style for the value and currency symbol, I've found myself in a position where a more manual workaround is required.
Any thoughts ?
You have everything you need in your SKProduct instance. Just use NSNumberFormatter in conjunction and that's it.
NSNumberFormatter *priceFormatter = [[NSNumberFormatter alloc] init];
[priceFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
for (SKProduct *product in response.products) {
[priceFormatter setLocale:product.priceLocale];
NSLog(#"Price for %# is: %#",product.localizedTitle,[priceFormatter stringFromNumber:product.price]);
}
Swift 3+
let priceFormatter = NumberFormatter()
priceFormatter.numberStyle = .currency
for product in response.products {
priceFormatter.locale = product.priceLocale
let localizedPrice = priceFormatter.string(from: product.price)
print("Price for \(product.localizedTitle) is: \(localizedPrice)")
}
The locale object doesn't seem to provide this information directly, but of course the number formatter must know it. You're not supposed to ask (new-style) number formatters for their format directly, although that'll probably work, and you can then look for the currency symbol, ¤, in the format string.
Possibly better would be to create a CFNumberFormatter, which does explicitly allow you to view its format, and then inspect that string:
// NSLocale and CFLocale are toll-free bridged, so if you have an existing
// NSNumberFormatter, you can get its locale and use that instead.
CFLocaleRef usLocale = CFLocaleCreate(NULL, CFSTR("en_US"));
CFNumberFormatterRef usFormatter = CFNumberFormatterCreate(NULL, usLocale, kCFNumberFormatterCurrencyStyle);
CFLocaleRef frLocale = CFLocaleCreate(NULL, CFSTR("fr_FR"));
CFNumberFormatterRef frFormatter = CFNumberFormatterCreate(NULL, frLocale, kCFNumberFormatterCurrencyStyle);
NSString * usString = (__bridge NSString *)CFNumberFormatterGetFormat(usFormatter);
NSString * frString = (__bridge NSString *)CFNumberFormatterGetFormat(frFormatter);
NSUInteger loc = ([usString rangeOfString:#"¤"]).location;
NSLog(#"Currency marker at beginning for US? %#", (loc == 0) ? #"YES" : #"NO");
loc = ([frString rangeOfString:#"¤"]).location;
NSLog(#"Currency marker at end for FR? %#", (loc == [frString length] - 1) ? #"YES" : #"NO");
I use this solution (Swift):
let currencyFormat = CFNumberFormatterGetFormat(CFNumberFormatterCreate(nil, locale, .CurrencyStyle)) as NSString
let positiveNumberFormat = currencyFormat.componentsSeparatedByString(";")[0] as NSString
let currencySymbolLocation = positiveNumberFormat.rangeOfString("¤").location
return (currencySymbolLocation == 0) ? .Before : .After
The accepted answer should be fixed since the CFNumberFormatterGetFormat sometimes (for some locales) returns double value: ¤##,#00.0;-¤##,#00.0 which includes a negative number format. Make sure to parse that string.
My solution for this was to set the decimal style and set the minimum number of significant digits.
static NSNumberFormatter *NumberFormatter;
if (!NumberFormatter) {
NumberFormatter = [[NSNumberFormatter alloc] init];
[NumberFormatter setNumberStyle:NSNumberFormatterDecimalStyle];
[NumberFormatter setUsesSignificantDigits:YES];
[NumberFormatter setMinimumSignificantDigits:2];
}
NSString *formattedNumberString = [NumberFormatter stringFromNumber:#(valueInEuro)];
NSString *stringInEuro = [NSString stringWithFormat:#"€ %#", formattedNumberString];
I have created an extension of SKProduct, putting things where they belong imho.
extension SKProduct
{
var localizedPrice: String {
let numberFormatter = NSNumberFormatter()
numberFormatter.numberStyle = .CurrencyStyle
numberFormatter.locale = self.priceLocale
numberFormatter.formatterBehavior = .Behavior10_4
return numberFormatter.stringFromNumber(self.price)!
}
}
That way of formatting is, by the way, also exactly what Apple suggests in the In-App Purchase Programming Guide, section Retrieving Product Information.
Swift 3
An extension function on Locale:
extension Locale {
func IsCurrenySymbolAtStart() -> Bool {
let currencyFormatter = NumberFormatter()
currencyFormatter.numberStyle = .currency
currencyFormatter.locale = self
let positiveFormat = currencyFormatter.positiveFormat as NSString
let currencySymbolLocation = positiveFormat.range(of: "¤").location
return (currencySymbolLocation == 0)
}
}
Usage:
let displayCurrencySymbolAtStart = NSLocale.current.IsCurrenySymbolAtStart()

NSParagraphStyle hypenationFactor not working

I'm trying to full justify and allow for hyphenation in an NSAttributedString. Right now I set the paragraph style using the code:
NSMutableParagraphStyle *paragraph = [[NSMutableParagraphStyle alloc] init];
paragraph.alignment = NSTextAlignmentJustified;
paragraph.hyphenationFactor = 0.5;
paragraph.lineBreakMode = NSLineBreakByWordWrapping;
This doesn't hyphenate, it just splits words based on how much they fit. I've also tried NSLineBreakByCharWrapping which ends up being even worse. Any ideas? I'm using the iOS 6 libraries.
Thanks,
Pete
Recently found this out:
There is a bug in the library, in that any value BETWEEN 0.0 and 1.0 does not work. That being said, the values 0.0 and 1.0 DO work. So to get hyphenation, set the hyphenation factor to 1.0 as of iOS6.

How do I convert an integer to the corresponding words in objective-c?

Is there a way in Objective-C on iOS to spell out an integer number as text?
For example, if I have
NSInteger someNumber = 11242043;
I would like to know some function so that would return a string similar to "eleven million two hundred forty two thousand forty three."
Apple has a lot of handy formatting functionality built in for many data types. Called a "formatter," they can convert objects to/from string representations.
For your case, you will be using NSNumberFormatter, but if you have an integer you need to convert it to an NSNumber first. See below example.
NSInteger anInt = 11242043;
NSString *wordNumber;
//convert to words
NSNumber *numberValue = [NSNumber numberWithInt:anInt]; //needs to be NSNumber!
NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
[numberFormatter setNumberStyle:NSNumberFormatterSpellOutStyle];
wordNumber = [numberFormatter stringFromNumber:numberValue];
NSLog(#"Answer: %#", wordNumber);
// Answer: eleven million two hundred forty-two thousand forty-three
If you'd like to learn more about formatters:
https://developer.apple.com/library/content/documentation/General/Conceptual/Devpedia-CocoaApp/Formatter.html
Power of extension for Swift 5
import Foundation
public extension Int {
var asWord: String? {
let numberValue = NSNumber(value: self)
let formatter = NumberFormatter()
formatter.numberStyle = .spellOut
return formatter.string(from: numberValue)
}
}
var value = 2
if let valueAsWord = value.asWord {
//do something with your worded number here
print("value worded = \(valueAsWord)")
} else {
print("could not word value :(")
}
Note: Edited to protect against formatter.string(from: returning nil which is highly not likely, but still possible.
Output:
value worded = two
From the docs:
NSNumberFormatterSpellOutStyle
Specifies a spell-out format; for example, “23” becomes “twenty-three”.
Available in iOS 2.0 and later.
Declared in NSNumberFormatter.h.
As your question isn't very specific, I won't post full-fledged code source either.
With Swift 5 / iOS 12.2, NumberFormatter has a numberStyle property that can be set with value NumberFormatter.Style.spellOut. spellOut has the following declaration:
case spellOut = 5
A style format in which numbers are spelled out in the language defined by the number formatter locale.
For example, in the en_US locale, the number 1234.5678 is represented as one thousand two hundred thirty-four point five six seven eight; in the fr_FR locale, the number 1234.5678 is represented as mille deux cent trente-quatre virgule cinq six sept huit.
This style is supported for most user locales. If this style doesn't support the number formatter locale, the en_US locale is used as a fallback.
The Playground code below shows how to convert an integer to a spell-out text using NumberFormatter spellOut style:
import Foundation
let integer = 2018
let formatter = NumberFormatter()
formatter.numberStyle = NumberFormatter.Style.spellOut
let spellOutText = formatter.string(for: integer)!
print(spellOutText) // prints: two thousand eighteen
We can do this in swift like this.
let formatter = NSNumberFormatter()
formatter.numberStyle = NSNumberFormatterStyle. SpellOutStyle
println("\(identifier) \(formatter.stringFromNumber(1234.5678))")
You can use the below function to convert an integer to words using swift native number style.
func toWords<N>(number: N) -> String? {
let formatter = NumberFormatter()
formatter.numberStyle = .spellOut
switch number {
case is Int, is UInt, is Float, is Double:
return formatter.string(from: number as! NSNumber)
case is String:
if let number = Double(number as! String) {
return formatter.string(from: NSNumber(floatLiteral: number))
}
default:
break
}
return nil
}
print(toWords(number: 12312))
print(toWords(number: "12312"))
For my own reference, this is #moca's answer, but ready for use:
- (NSString *) spellInt:(int)number {
NSNumber *numberAsNumber = [NSNumber numberWithInt:number];
NSNumberFormatter *formatter = [NSNumberFormatter new];
[formatter setNumberStyle:NSNumberFormatterSpellOutStyle];
return [formatter stringFromNumber:numberAsNumber];
}
Note: This is using ARC.