NSNumberFormatter: dispatch_once where Currency changes - objective-c

I have a tableview where a list of NSDecimalNumber will be displayed with their own currencies.
I use a dispatch_once on a NSNumberFormatter but notice that sometimes it'll totally ignore my commands to set the fraction digits or suffix on fraction digits altogether.
Currently the only way I've been able to resolve this is to init the NSNumberFormatter every row which is a bit silly.
ie:
- (NSNumberFormatter *)currencyFormatWithCurrency:(NSString *)currency
{
static NSNumberFormatter *_formatter = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_formatter = [[NSNumberFormatter alloc] init];
});
[_formatter setCurrencyCode:currency];
[_formatter setNumberStyle:NSNumberFormatterCurrencyStyle];
[_formatter setMaximumFractionDigits:0];
[_formatter setGeneratesDecimalNumbers:NO];
[_formatter setRoundingMode:NSNumberFormatterRoundUp];
return _formatter;
}
// someCurrency is a string that will change per row
// someAmount is a NSDecimalNumber that will change per row
NSNumberFormatter *formatter = [self currencyFormatWithCurrency:someCurrency];
NSString *formattedAmount = [formatter stringFromNumber:someAmount];
self.amountLabel.text = formattedAmount;
Sometimes with the above if I feed in a figure like 80000, it'll return 80,000.00 (sometimes it depends on the currency).
I guess I could just init the NSNumberFormatter and nil it on each row.
Perhaps I'm doing it wrong, but if there is perhaps a way to ensure that I don't need to keep creating the NSNumberFormatter to ensure it'll understand the rules I give it?
Many thanks

NSNumberFormatter is not thread safe. That will lead to all kinds of problems. (Well, the way you use it cannot really be thread safe, because you might be modifying it in one thread while another thread is using it, but just using an NSNumberFormatter from two threads is not safe).
You can always do something in a method like
NSString* lastCurrency = nil;
NSNumberFormatter* formatter = nil;
for (;;) {
...
NSString* currency = ...;
if (! [lastCurrency isEqualToString:currency]) {
lastCurrency = currency;
formatter = ...;
}
}
Works well if in most cases the currency is unchanged.

I think this is a possible solution which I got from the following website might help things.
NSString *HTCurrencyString(double amount, NSString *currencyCode)
{
static NSString *currencyFormatterKey = #"HTCurrencyFormatter";
NSNumberFormatter *currencyFormatter = [[NSThread currentThread] threadDictionary][currencyFormatterKey];
if (currencyFormatter == nil)
{
currencyFormatter = [[NSNumberFormatter alloc] init];
[currencyFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
[currencyFormatter setLocale:[NSLocale currentLocale]];
[[NSThread currentThread] threadDictionary][currencyFormatterKey] = currencyFormatter;
}
[currencyFormatter setCurrencyCode:currencyCode];
[currencyFormatter setMaximumFractionDigits:0];
[currencyFormatter setRoundingMode:NSNumberFormatterRoundHalfUp];
return [currencyFormatter stringFromNumber:#(amount)];
}

Related

Using NSNumber to sort numerous integers

Ive been doing alot of research and can't quite grasp resolving this issue. In my application I have several text fields that save integer values in the form of int variables (at least 15 int variables). My main goal is to sort out high to low numbers that were saved from the text fields.
Now would I use the following code to convert each seperate integer variable into a new NSNumber, than use a sort function to sort out the new NSNumbers from high to low?
NSNumber *newNumber = [NSNumber numberWithInt: my_int_variable];
I just feel as if this is a redundant way of sorting 12 integer variables and there is a easier way. Thank you for the help
Going from individual text fields to strings to ints to NSNumbers to an array to a sorted array is long, clunky trip. But a worthwhile trip nevertheless.
// assume these
UITextField *field0;
UITextField *field1;
UITextField *field2;
// make an array for the input and the result
NSArray *textFields = #[field0, field1, field2];
NSMutableArray *numbers = [#[] mutableCopy];
// prepare a number formatter
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
formatter.numberStyle = NSNumberFormatterDecimalStyle;
// lots of choices here. see https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSNumberFormatter_Class/index.html
for (UITextField *textField in textFields) {
NSString *text = textField.text;
NSNumber *number = [formatter numberFromString:text];
[numbers addObject:number];
}
// sort...a few choices here, too. taking the simplest:
[numbers sortUsingSelector:#selector(compare:)];
NSLog(#"ta da: %#", numbers);
Or did you want to sort the text fields based on their contents? Doable too:
NSMutableArray *textFields = [#[field0, field1, field2] mutableCopy];
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
formatter.numberStyle = NSNumberFormatterDecimalStyle;
[textFields sortedUsingComparator: ^(id objA, id objB) {
NSString *textA = ((UITextField *)objA).text;
NSString *textB = ((UITextField *)objB).text;
NSNumber *numberA = [formatter numberFromString:textA];
NSNumber *numberB = [formatter numberFromString:textB];
return [numberA compare:numberB];
}];
NSLog(#"text fields in order of their contents: %#", textFields);

Convert a string to a positive or negative number

I am new to ObjC. I've spent years working in Applescript and I've decided to move up. I am a hobbiest programmer.
I have the following code:
+(NSArray *) initArrayWithFileContents:(NSString *) theFilePath
{
NSString *theContents = [(self) loadFile:theFilePath]; // returns the contents of a text file
NSArray *theParagraphs = [(self) getParagraphs:theContents]; // returns the contents as an array of paragraphs
NSMutableArray *teamData = [[NSMutableArray alloc] init]; // array of team data
NSMutableArray *leagueData = [[NSMutableArray alloc] init]; // array of arrays
NSNumberFormatter *numberStyle = [[NSNumberFormatter alloc] init];
[numberStyle setNumberStyle:NSNumberFormatterDecimalStyle];
for (NSString *currentParagraph in theParagraphs)
{
NSArray *currentTeam = [(self) getcolumnarData:currentParagraph];
for (NSString *currentItem in currentTeam)
{
NSNumber *currentStat = [numberStyle numberFromString:currentItem];
if (currentStat != Nil) {
[teamData addObject:currentStat];
} else {
[teamData addObject:currentItem];
}
}
[leagueData addObject:teamData];
[teamData removeAllObjects];
}
return leagueData;
}
This works fine for strings and for negative numbers, but a number preceded by a "+" sign is returned as a string. I figure I need to use a different number formatter style but I don't know what to use.
Thanks in advance,
Brad
NSNumberFormatter *numberStyle = [[NSNumberFormatter alloc] init];
[numberStyle setNumberStyle:NSNumberFormatterDecimalStyle];
[numberStyle setPositiveFormat:#"'+'#"] ;
or
NSNumberFormatter *numberStyle = [[NSNumberFormatter alloc] init];
[numberStyle setNumberStyle:NSNumberFormatterDecimalStyle];
[numberStyle setPositivePrefix:#"+"] ;
You could remove the + sign if one exists:
if ([currentItem hasPrefix:#"+"])
{
currentItem = [currentItem substringWithRange:NSMakeRange(1, [currentItem length] -1)];
}
Probably a better way, but this would work.
What ended up working for me was to create 2 NSNumberFormatters; 1 for decimals and 1 for decimals using the setPositiveFormat: method as described above. If the first formatter doesn't work, it'll flow on to the next formatter using the positive format.

Getting nil from NSNumberformater numberFromString

I'm trying to format an amount from a .txt file coming in es_US locale(x,xxx.xx), to my current locale with is es_ES(x.xxx,xx). I would expect that [NSNumberFormater numberFromString] would just reformat the string, however and I'm only getting a nil value from this method.
I also tried another approach after checking the answers from here, but NSDecimalnumber does not work if the string has thousand separators, so if anybody could tell me what am I doing wrong please...
- (void) setSaldo_sap:(NSString *)saldo_sap
{
NSNumberFormatter *numFormatter = [[NSNumberFormatter alloc] init];
[numFormatter setLocale:[NSLocale currentLocale]];
[numFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4];
[numFormatter setNumberStyle:NSNumberFormatterDecimalStyle];
[numFormatter setNegativeFormat:#"-¤#,##0.00"];
//saldo_sap = #" -324,234.55"
NSString * tmpString = [saldo_sap stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSNumber *num = [numFormatter numberFromString:tmpString];
NSDecimalNumber *tempNumber = [NSDecimalNumber decimalNumberWithString:tmpString];
_saldo_sap = [numFormatter stringFromNumber:tempNumber];
}
I think you misinterpret the aim of NSNumberFormatter: it doesn't "reformat", it "formats" and "parses" a numbers formatted along the set rules. So if you have numbers coming in "es_US" locale but want to format them using "es_ES" you will need two NSNumberFormatters: one for each locale.
Parse the incoming number with "es_US" and format using "es_ES", simplifying a bit (I don't know those two locales and the exact format of your numbers so you may need to tweek it a bit):
NSString * tmpString = ...
NSNumberFormatter *usFormatter = [[NSNumberFormatter alloc] init];
[usFormatter setLocale:[[[NSLocale alloc] initWithLocaleIdentifier: #"es_US"] autorelease]];
[usFormatter setHasThousandSeparators: YES];
NSNumberFormatter *esFormatter = [[NSNumberFormatter alloc] init];
[esFormatter setLocale:[[[NSLocale alloc] initWithLocaleIdentifier: #"es_ES"] autorelease]];
[esFormatter setHasThousandSeparators: YES];
// this assignment looks also somewhat weird: is it an instance variable?
// 'cause if it is and you assign an autoreleased string you'll have a bad pointer there
_saldo_sap = [esFormatter stringFromNumber: [usFormatter numberFromString: tmpString]];
// And unless you use ARC you leak your formatter on each call, so at the end
[usFormatter release];
[esFormatter release];
EDIT
If the input strings contain prefix/postfix characters, that may prevent NSNumberFormatter to work (it use usually pretty strict), use setLenient::
"Sets whether the receiver will use heuristics to guess at the number which is intended by a string."
If you have more than one number to be converted, do not create the formatters for each number, this is just a waste of memory and cpu. Make them instance variables and reuse. It will be much clearer than just having one formatter and reconfiguring it between parsing one format and formatting in another.
NSString *_saldo_sap = #" -324234.55";
//NSString *_saldo_sap = #" 324,234.55";
NSString * tmpString = [_saldo_sap stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
//tmpString = #"-324,234.55"
NSNumberFormatter *numFormatter = [[NSNumberFormatter alloc] init];
NSNumber *num = [numFormatter numberFromString:tmpString];
[numFormatter setLocale:[NSLocale currentLocale]];
[numFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4];
[numFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
[numFormatter setNegativeFormat:#"-¤#,##0.00"];
_saldo_sap = [numFormatter stringFromNumber:num];
Firstly, for getting NSNumber from NSString the string must be in correct readable format i.e. it must not include any characters like " , " as stated.
Secondly, you must first convert the string to NSNumber and then format it accordingly.

Converting Decimal to String to Decimal

I copied this code from another post. I tried the example, however, I am getting a EXEC_BAD_ACCESS. From what I have read, this error happens when trying to use an object that has been deallocated, but I just don't see where I am doing that:
The call
...
float weighted_average = num_of_passes / total_of_all_passes;
NSString *newNumber = [[NSString alloc] init];
newNumber = [self formattedStringWithDecimal:weightedAverage]; //weighted average (float) = 15.875145
...
The Function
- (NSString *)formattedStringWithDecimal:(NSDecimalNumber *)decimalNumber
{
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
[formatter setMaximumFractionDigits:2]; //two deimal spaces
[formatter setRoundingMode: NSNumberFormatterRoundHalfUp]; //round up
NSString *result =[NSString stringWithString:[formatter stringFromNumber:decimalNumber]];
[formatter release];
return result;
}
Call the method like this:
newNumber = [self formattedStringWithDecimal:[NSDecimalNumber numberWithFloat:15.434]];
You've tried to pass a primitive, but the method expects an object: an NSDecimalNumber. You've got to use the static convenience method numberWithFloat to create an object of that type.
And by the way, I have the feeling that
newNumber = [NSString stringWithFormat#"%.2f", 15.434];
could achieve the same result with less lines of code. Note this will not round up your number though.
You are returning an autoreleased object. Does the function that uses it retain it? If not, it could be released and then later (later run loop) its trying to be (re)used. Agreed on the enabling zombies to spot that kind of thing.

Returning a string

I have just started with Objective-C and have a (probably) very, very basic question/problem.
int testf(int x){
NSDateFormatter *dateformatter=[[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"dd.MM.yyyy"];
NSString *infstr=[dateFormatter stringFromDate:[[NSDate] dateByAddingTimeInterval:(60*x)];
return infstr;
}
NSString *testString=testf(1);
I currently have the following problem: I don't know how to return a string from a function. I couldn't even figure it out through Google.
The aforementioned Code leads to a warning "Pointer from integer without a cast". I hope anyone does know a solution to this "problem" and is willing to share it with me.
Thanks in advance.
You need to create an NSString object using the int.
NSString *testf(int x) {
return [NSString stringWithFormat:#"%i",x];
}
NSString *testString = testf(1);
Just change your return type from int to an NSString pointer like so:
NSString* testf(int x){
NSDateFormatter *dateformatter=[[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"dd.MM.yyyy"];
NSString *infstr=[dateFormatter stringFromDate:[NSDate dateByAddingTimeInterval:(60*x)]];
[dateFormatter release];
return infstr;
}
NSString *testString=testf(1);
Also your dateFormatter is leaking, so add the release statement as shown above.