How do I test that my NSNumberFormatter is only initialized once? - ios

I created a category for NSDecimalNumber in which I take an NSString and return a NSDecimalNumber. I'm using it in a few of my view controllers and wanted to create a single global instance of NSNumberFormatter. I think this works but I have no idea on how to test it. For example, I want to NSLog every time an NSNumberFormatter instance gets allocated. How do I do that?
#import "NSDecimalNumber+amountFromTextField.h"
#implementation NSDecimalNumber (amountFromTextField)
static NSNumberFormatter *nf;
+(NSDecimalNumber *)amountFromTextField:(NSString *)amount {
#synchronized(self) {
if (nf == nil) {
nf = [[NSNumberFormatter alloc] init];
}
}
NSDecimal _amount = [[nf numberFromString:amount] decimalValue];
return [NSDecimalNumber decimalNumberWithDecimal:_amount];
}
#end

You can extract the formatter allocation into another method, and assert on that:
+ (NSNumberFormatter*)amountFormatter;
- (void)testOnlyOneFormatterIsCreated {
NSNumberFormatter *formatter1 = [NSDecimalNumber amountFormatter];
NSNumberFormatter *formatter2 = [NSDecimalNumber amountFormatter];
XCTAssertEqual(formatter1, formatter2, "Expected only one formatter to be created");
}
The problem with your current implementation is the fact you don't have access to the created formatter, thus it very hard to test it. Splitting object creation and business logic into separate units is also good for your code, as it keeps your units short and focused on one task only.

Related

Passing a NSNumber from one method to another

I'll just show the example it's easier than words.
.h file
#interface Something : UITableViewController <UIAlertViewDelegate, GADBannerViewDelegate>
{
NSNumber *myNumber;
}
.m file
-(void) someMethod1
{
NSLog #("is it reaching here? %#", myNumber);
/// returns Null
}
-(void) someMethod2
{
FixturesObject *closestObject;
NSTimeInterval closestInterval = DBL_MAX;
for (FixturesObject *myObject in newFixtureObjectArray) {
if (myObject != nullValue) {
NSTimeInterval interval = ABS([myObject.date2 timeIntervalSinceDate:[NSDate date]]);
if (interval < closestInterval) {
closestInterval = interval;
closestObject = myObject;
}
}
roundFinder = closestObject.round;
NSNumberFormatter * f = [[NSNumberFormatter alloc] init];
[f setNumberStyle:NSNumberFormatterNoStyle];
myNumber = [f numberFromString:roundFinder];
}
NSLog(#"What is my number? %#", myNumber);
// this returns like.. 26
}
How do i pass the value from the method below to another method? It's not working for me at all.
Thank you for your time.
myNumber is part of the same object where you have the methods. You don't need to pass it anywhere. You just need to create a property setter/getter.
The method you are sending the number has to be able to accept the number as parameter, something like this:
-(void) someMethod1:(NSNumber*)number{
if (number) NSLog #("is it reaching here? %#", myNumber);
/// returns Null
}
But in this case, have a look at what #Black Frog told you. You don't really need to pass this one as it is "all around".

How do I get NSNumber with NSLocale smarts from a UITextField?

Getting correct localized formatting of numbers to display is easy.
But prompting for a decimal number input from a UITextField is proving tricky.
Even though the decimal keypad may present the comma for European usage instead of the stop, the method handling the input apparently still needs to be locale-savvy.
my research here on S.O. and other places suggests
-(IBAction)buttonPress:(UIButton *)sender
{
NSNumber *firstNumber = #([self.firstField.text floatValue]); // 2 alternative ways
NSNumber *secondNumber = [NSNumber numberWithFloat:[self.secondField.text floatValue]];
.
.
only gives me integers to perform arithmetic with. They aren't floats at all. They have no float value
Supposing this is the only thing the app does, and I immediately do:
.
.
NSNumberFormatter *numberFormat = [[ NSNumberFormatter alloc] init];
[ numberFormat setNumberStyle:NSNumberFormatterDecimalStyle];
[ numberFormat setLocale:[NSLocale currentLocale]];
[ numberFormat setMaximumFractionDigits:2];
NSString *displayString = [ numberFormat stringFromNumber:firstNumber];
self.resultLabel.text = displayString;
}
I am throwing the input back to a label in the ViewController without any further handling to make sure the the decimals aren't hidden by not having a formatter. No Joy. Still in integers.
Obviously the app has to do something in the way of calculation, and since one can't handle NSNumber wrappers directly, I have to resolve it before i do the conversion thing:
double xD = [firstNumber doubleValue];
double yD = [secondNumber doubleValue];
the question is, where is the decimal-ness of the input being lost?
I have no problem with the desktop keyboard and the simulator, but if i set a test device to - say - Polish then that simply won't do.
EDIT:
and here's the result from the answer provided below
// run these 4 methods first, that way you can re-use *numberFormat for the display
NSNumberFormatter *numberFormat = [[ NSNumberFormatter alloc] init];
[ numberFormat setNumberStyle:NSNumberFormatterDecimalStyle];
[ numberFormat setLocale:[NSLocale currentLocale]];
[ numberFormat setMaximumFractionDigits:2];
// here 'tis
NSNumber *firstNumber = [numberFormat numberFromString:self.firstField.text];
// do the same for other input UITextFields
// sh-boom. there you go
You need use longLongValue not longValue to convert NSString to long type. or use NSNumberFormatter->numberFromString:

NSNumberFormatter to add and remove decimals

what is the best way on how to deal with integers, NSStrings and NSNumberFormatter ?
I have an app with an UITextField, where users can input numbers. By every change in the UITextField, my app is immediately taking the input, put it in my function that makes a calculation, and outputs the result in an UILabel.
people can work with large numbers here, so in order to make it easier for them to read what their input is, I decided that I want to make it possible so that the input changes as soon as they insert it.
For example, when you type in 1000 that it automatically changes into 1,000. when you type in 100000000000 it automatically changes into 100,000,000,000 etc.
This I was able to do myself, like this:
formatter = [[NSNumberFormatter alloc] init];
[formatter setNumberStyle:NSNumberFormatterDecimalStyle];
self.inputField.text = [formatter stringFromNumber:[formatter numberFromString:self.inputField.text]];
maybe not the best way, but it works.
my function expects an int as input, so up till 999 it's very easy: i can do the following:
[self.inputField.text integerValue];
and it works
but whenever I have 1000 or higher in my UITextField, self.inputField.text property will be 1,000 .. and then things go wrong with using the integerValue from it.
So my question is, how can I go back from NSString to an int, when there are decimals in it ?
Use the -numberFromString function to get an NSNumber from the string, then use the intValue from the NSNumber class to get the integer value:
NSString *string = #"1,000";
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
[formatter setNumberStyle:NSNumberFormatterDecimalStyle];
NSNumber *number = [formatter numberFromString:string];
int intValue = [number integerValue];// intValue = 1000

iOS Method Return Multiple Values NSDictionary

I have a method that I call that calculates the Sunrise, Noon and Sunset for any given day. I pass the method the day date as a Julian.
The method need to return the three numbers or strings: Sunrise, Noon and Sunset.
I am trying to call it as follows:
ClassSolarCalculations *LINK = [[ClassSolarCalculations alloc] init];
NSString dateSunrise= [[NSString alloc] initWithFormat:#"%f", [LINK CalculateSunrise: Julian]];
where the Method reads:
(NSDictionary *) CalculateSunrise: (double) Julian;
NSDictionary *returnTimes = [NSDictionary initWithObjectsAndKeys: SunriseText, #"Sunrise", NoonText, "#Noon", SunsetText, #"Sunset", nil];
return returnTimes;
I can this approach to work to return a single value but would like to return all three in one go rather than fudge the solution by calling variants of the routine three times…
Lots of things should be changed here:
method and variable names should start with lowercase letters and use camel case.
Rename your CalculateSunrise: method since it will return more values. Maybe calculateSunTimes:.
Since your method returns an NSDictionary you handling of the return needs to be different.
Try this:
ClassSolarCalculations *link = [[ClassSolarCalculations alloc] init];
NSDictionary *times = [link calculateSunTimes:julian];
NSString *sunrise = times[#"sunrise"];
NSString *noon = times[#"noon"];
NSString *sunset = times[#"sunset"];
Your method would be something like:
- (NSDictionary *)calculateSunTimes:(double)julian {
// calculate the three values:
return #{ #"sunrise" : sunriseText, #"sunset" : sunsetText, #"noon" : noonText };
}
Notice the use of modern Objective-C syntax.

Objective C - iOS - verify float/double

I am working on the infamous Stanford calculator assignment. I need to verify inputted numbers for valid float values, so we can handle numbers like 102.3.79.
To avoid having to write a little loop to count periods in the string, there's got to be a built-in function yeah?
You can use the C standard library function strtod(). It stops where it encounters an error, and sets its output argument accordingly. You can exploit this fact as follows:
- (BOOL)isValidFloatString:(NSString *)str
{
const char *s = str.UTF8String;
char *end;
strtod(s, &end);
return !end[0];
}
There's at least one fairly elegant solution for counting #"." in a string:
NSString *input = #"102.3.79";
if([[input componentsSeparatedByString:#"."] count] > 2) {
NSLog(#"input has too many points!");
}
Digging a little deeper... If you're looking to validate the whole string as a number, try configuring an NSNumberFormatter and call numberFromString: (NSNumberFormatter documentation).
Having gone through CS193P, I think the idea is to get comfortable with NSString and UILabel versus using C. I would look into having a simple decimal point BOOL flag, as buttons are pressed and you are concatenating the numbers 1- for use and 2- for display.
This will come in handy as well when you are doing other checks like hanging decimal points at the end of the number or allowing the user to backspace a number.
Edited for example:
Create an IBAction connected to each number button:
- (IBAction)numberButtonPressed:(UIButton *)sender
{
if([sender.titleLabel.text isEqualToString:#"."])
{
if (!self.inTheMiddleOfEnteringANumber)
self.display.text=[NSString stringWithString:#"0."];
else if (!self.decimalPointEntered)
{
self.display.text=[self.display.text stringByAppendingString:sender.titleLabel.text];
self.decimalPointEntered=TRUE;
}
}
self.inTheMiddleOfEnteringANumber=TRUE;
}
-(BOOL) isNumeric:(NSString*)string {
NSNumberFormatter *formatter = [NSNumberFormatter new];
[formatter setNumberStyle:NSNumberFormatterDecimalStyle];
NSNumber *number = [formatter numberFromString:string];
[formatter release]; // if using ARC remove this line
return number!=nil;
}
-(BOOL) isFloat:(NSString*)string {
NSScanner *scanner = [NSScanner scannerWithString:string];
[scanner scanFloat:NULL];
return [scanner isAtEnd];
}

Resources