Struggling with currency in Cocoa - cocoa

I'm trying to do something I'd think would be fairly simple: Let a user input a dollar amount, store that amount in an NSNumber (NSDecimalNumber?), then display that amount formatted as currency again at some later time.
My trouble is not so much with the setNumberStyle:NSNumberFormatterCurrencyStyle and displaying floats as currency. The trouble is more with how said numberFormatter works with this UITextField. I can find few examples. This thread from November and this one give me some ideas but leaves me with more questions.
I am using the UIKeyboardTypeNumberPad keyboard and understand that I should probably show $0.00 (or whatever local currency format is) in the field upon display then as a user enters numerals to shift the decimal place along:
Begin with display $0.00
Tap 2 key: display $0.02
Tap 5 key: display $0.25
Tap 4 key: display $2.54
Tap 3 key: display $25.43
Then [numberFormatter numberFromString:textField.text] should give me a value I can store in my NSNumber variable.
Sadly I'm still struggling: Is this really the best/easiest way? If so then maybe someone can help me with the implementation? I feel UITextField may need a delegate responding to every keypress but not sure what, where and how to implement it?! Any sample code? I'd greatly appreciate it! I've searched high and low...
Edit1: So I'm looking into NSFormatter's stringForObjectValue: and the closest thing I can find to what benzado recommends: UITextViewTextDidChangeNotification. Having really tough time finding sample code on either of them...so let me know if you know where to look?

My solution:
- (BOOL)textField:(UITextField *)textField
shouldChangeCharactersInRange:(NSRange)range
replacementString:(NSString *)string
{
// Clear all characters that are not numbers
// (like currency symbols or dividers)
NSString *cleanCentString = [[textField.text
componentsSeparatedByCharactersInSet:
[[NSCharacterSet decimalDigitCharacterSet] invertedSet]]
componentsJoinedByString:#""];
// Parse final integer value
NSInteger centAmount = cleanCentString.integerValue;
// Check the user input
if (string.length > 0)
{
// Digit added
centAmount = centAmount * 10 + string.integerValue;
}
else
{
// Digit deleted
centAmount = centAmount / 10;
}
// Update call amount value
[_amount release];
_amount = [[NSNumber alloc] initWithFloat:(float)centAmount / 100.0f];
// Write amount with currency symbols to the textfield
NSNumberFormatter *_currencyFormatter = [[NSNumberFormatter alloc] init];
[_currencyFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
[_currencyFormatter setCurrencyCode:#"USD"];
[_currencyFormatter setNegativeFormat:#"-ยค#,##0.00"];
textField.text = [_currencyFormatter stringFromNumber:_amount];
[_currencyFormatter release]
// Since we already wrote our changes to the textfield
// we don't want to change the textfield again
return NO;
}

Here's the rough plan of attack I'd use if I had to write that now. The trick will be typing into a hidden UITextField and updating a UILabel with the formatted value as the user types.
Create a UITextField, make it hidden, assign it a delegate, and then make it the first responder to summon the keyboard.
In your delegate, respond to the textDidChange: message (too lazy to look up the exact name) by taking the text field's new value and converting it to a number. Make sure empty string converts to zero.
Run the number through your formatter, and update a UILabel with that formatted currency value.
On every key press, the label will be updated, so the user will feel as though she is editing the formatted value, when she is really editing the hidden text field. How sneaky!

Well, i think this is better:
- (void)viewWillDisappear:(BOOL)animated{ [[NSNotificationCenter defaultCenter] removeObserver:self name:UITextFieldTextDidChangeNotification object:nil]; }
-(void)viewDidAppear:(BOOL)animated{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(textDidChanged:) name:UITextFieldTextDidChangeNotification object:nil];
}
-(void)textDidChanged:(NSNotification *)notification{
[[NSNotificationCenter defaultCenter] removeObserver:self name:UITextFieldTextDidChangeNotification object:nil];
UITextField * textField= [notification object];
NSString * sinPesos= [textField.text stringByReplacingOccurrencesOfString:#"$" withString:#""];
NSString * sinPuntos= [sinPesos stringByReplacingOccurrencesOfString:#"." withString:#""];
float monto = [sinPuntos floatValue];
monto= monto/100;
NSString * cardText= [[self montoFormatter] stringFromNumber:[NSNumber numberWithDouble:monto]];
textField.text = ([cardText isEqualToString: #"0"] ? #"":cardText);
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(textDidChanged:) name:UITextFieldTextDidChangeNotification object:nil];
}
-(NSNumberFormatter *)montoFormatter{
NSNumberFormatter* numberFormatter = [[NSNumberFormatter alloc] init];
[numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
[numberFormatter setMaximumFractionDigits:2];
return [numberFormatter autorelease];
}
Try it out :)

Since I am lazy, and I can't stand having my input reformatted "to help me out" on the fly, I say:
Just let them enter a decimal number. When they leave the field, reformat it.

Well, this is a bit late but I figured I would give this a shot anyway. This is probably more of a workaround and may be messy code, but it worked for me. I connected a label and a hidden textfield in IB, and I wrote this code using the UITextFieldDelegate delegate.
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
if (textField == fineHiddenTextField) {
NSString *fineText = [textField.text stringByReplacingCharactersInRange:range withString:string];
if ([fineText length] == 0) {
NSString *emptyFine = #"0.00";
float fineValue = [emptyFine floatValue];
fineEntryLabel.text = [NSString stringWithFormat:#"%.2f", fineValue];
} else if ([fineText length] == 1) {
NSString *firstDec = [NSString stringWithFormat:#"0.0%#", fineText];
float fineValue = [firstDec floatValue];
fineEntryLabel.text = [NSString stringWithFormat:#"%.2f", fineValue];
} else if ([fineText length] == 2) {
NSString *secondDec = [NSString stringWithFormat:#"0.%#", fineText];
float fineValue = [secondDec floatValue];
fineEntryLabel.text = [NSString stringWithFormat:#"%.2f", fineValue];
} else {
int decimalPlace = [fineText length] - 2;
NSString *fineDollarAmt = [fineText substringToIndex:decimalPlace];
NSString *fineCentsAmt = [fineText substringFromIndex:decimalPlace];
NSString *finalFineValue = [NSString stringWithFormat:#"%#.%#", fineDollarAmt, fineCentsAmt];
float fineValue = [finalFineValue floatValue];
fineEntryLabel.text = [NSString stringWithFormat:#"%.2f", fineValue];
}
//fineEntryLabel.text = [NSString stringWithFormat:#"%.2f", fineValue];
return YES;
}
}
Not exactly the neatest, but it really got the job done. The initial if statement was just to make sure that this was only happening for this one particular textField (As there are multiple throughout the same page.) This code was intended for the input of money (The amount of a fine paid) and I wanted it to be simple and easy to use. Just set your label to align from the right, and it should.
I am a little wired on coffee right now, but I will answer any questions you may have. :)

Here's my solution!
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string{
float valorUnit = [textField.text floatValue];
if( string.length > 0)
{
float incremento = [string floatValue];
valorUnit = valorUnit * 10.f + (incremento/100.f);
NSString* strVal = [NSString stringWithFormat:#"%f", valorUnit];
if (valorUnit > 0.f && valorUnit < 10.f) {
textField.text = [strVal substringToIndex:3];
}
else if (valorUnit < 100.f && valorUnit >= 10.f)
{
textField.text = [strVal substringToIndex:4];
}
else if (valorUnit >=100.f && valorUnit <1000.f)
{
textField.text = [strVal substringToIndex:5];
}
else {
return NO;
}
}
else {
valorUnit = (valorUnit/10.f);
NSString* strVal = [NSString stringWithFormat:#"%f", valorUnit];
if (valorUnit > 0.f && valorUnit < 10.f) {
textField.text = [strVal substringToIndex:5];
}
else if (valorUnit < 100.f && valorUnit >= 10.f)
{
textField.text = [strVal substringToIndex:6];
}
else if (valorUnit >=100.f && valorUnit <1000.f)
{
textField.text = [strVal substringToIndex:7];
}
else {
return NO;
}
}
return YES;
}

I wrote an open source UITextField subclass to handle this, available here:
https://github.com/TomSwift/TSCurrencyTextField
The approach I took is similar to what Lars Schneider suggests in his popular answer. But my version is fully encapsulated in a reusable component that you use anywhere, just like UITextField. If you choose to implement any UITextFieldDelegate methods you can, but this is not required.

Related

MapView Problems - Pin change color when map reloded

I have a problem with the PIn color mapView when a refresh is done.
In my I app i display some point with two color in order to identify if a service is available.
On the first start, no problems appear. The code is the follower:
- (void)viewDidLoad
{
[super viewDidLoad];
[self dowloadPoint]; // here I exucte the first start
}
- (void)dowloadPoint{
NSURL *url1 =[NSURL URLWithString:#"http:MYUSRL"];
NSData *datos1 =[[NSData alloc] initWithContentsOfURL:url1];
[self plotBarPosition:datos_string1]; //Here I call the plotBarPosition method
}
- (void)plotBarPosition:(NSString *)datos_string1 {
for (id<MKAnnotation> annotation in _mapView.annotations) {
[_mapView removeAnnotation:annotation];
}
// Parse the string into JSON
NSDictionary *json = [(NSDictionary*)[datos_string1 JSONValue]objectForKey:#"features"];
// Get the objects you want, e.g. output the second item's client id
NSArray *items_properties = [json valueForKeyPath:#"properties"];
NSArray *items_geo = [json valueForKeyPath:#"geometry"];
for (int i = 0; i < [json count]; i++){
NSString *nomprePunto =[[items_properties objectAtIndex:i] objectForKey:#"title"];
NSNumber *lat =[[[items_geo objectAtIndex:i] objectForKey:#"coordinates"] objectAtIndex:0];
NSNumber *lon =[[[items_geo objectAtIndex:i] objectForKey:#"coordinates"] objectAtIndex:1];
CLLocationCoordinate2D coordinate;
coordinate.latitude = lat.doubleValue;
coordinate.longitude = lon.doubleValue;
//ESTADO
NSString *description = [[items_properties objectAtIndex:i] objectForKey:#"description"];
NSString *estado_punto = [[NSString alloc]init];
if ([description rangeOfString:#"Averiado"].location == NSNotFound) {
estado_punto = #"Available";
} else {
estado_punto = #"NOt Available";
averiados ++;
}
NSString *averiadosStr = [NSString stringWithFormat:#"%d",averiados];
averiadosLabel.text = averiadosStr;
MyLocation *location =[[MyLocation alloc] initWithName:nomprePunto coordinate:coordinate estado:estado_punto];
[_mapView addAnnotation:location];
}
}
- (MKPinAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(MyLocation *)annotation {
static NSString *identifier = #"MyLocation";
if ([annotation isKindOfClass:[MyLocation class]]) {
MKPinAnnotationView *annotationView = (MKPinAnnotationView *) [_mapView dequeueReusableAnnotationViewWithIdentifier:identifier];
if (annotationView == nil) {
annotationView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:identifier];
annotationView.enabled = YES;
annotationView.canShowCallout = YES;
if([[annotation estado] isEqualToString:#"En Servicio"])
annotationView.pinColor = MKPinAnnotationColorGreen;
} else {
annotationView.annotation = annotation;
}
return annotationView;
}
return nil;
}
But whe I add a refres button that is function is simply a refreshcalling the dowloadPoint once again,
- (IBAction)refresh{
[self dowloadPoint];
}
the color of pins change in a "random manner", not corrisponding with the real state of point.
Any ideas about what is happening? Thanks in advance.
EDIT: It seemps pproblems is due to:
for (id<MKAnnotation> annotation in _mapView.annotations) {
[_mapView removeAnnotation:annotation];
}
erasing it, the app work properly but pins area drown abow the previous ones...:S
The default color of the pin is red. You set it to green if the estado property of your MyLocation object is equal to #"En Servicio". I understand that sometimes the color is red, when your estado property is equal to #"En Servicio", or sometimes green when it is not.
One reason could be that your MyLocation object simply does no longer exist when you press the refresh button. In this case, you might still have a pointer to the memory location where it once existed, but this location may have been overwritten by anything, causing a random color.
This can happen e.g. if your MyLocation object has been created as an autorelease object that has been released when you returned to the main event loop, i.e. to handle user interactions.
This should not be the case if you are using ARC.

NSPasteboard readObjectsForClasses:options: returns value twice

Right to the point, then:
First snippet (AppDelegate):
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
//...code taken out...
[NSEvent addGlobalMonitorForEventsMatchingMask:NSKeyDownMask handler:^(NSEvent *incomingEvent) {
if ([incomingEvent type] == NSKeyDown) {
NSUInteger flags = [incomingEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask;
if (flags==NSCommandKeyMask && ([incomingEvent keyCode] == 8)) {
[ClipboardUtilities logger:#"cmd+c recognized"];
[self determineAndAddToHistory];
}
}
}];
}
Second snippet (AppDelegate):
-(void) determineAndAddToHistory {
id clipDat = [ClipboardUtilities getClipboardDataNatively];
if ([clipDat isKindOfClass:[NSAttributedString class]])
NSLog(#"clipDat.string = %#",((NSAttributedString*)clipDat).string);
}
Third snippet (ClipboardUtilities class):
+(id) getClipboardDataNatively {
NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
NSArray *classArray = #[[NSAttributedString class], [NSImage class]];
NSDictionary *options = [NSDictionary dictionary];
NSArray *objectsToPaste = nil;
BOOL ok = [pasteboard canReadObjectForClasses:classArray options:options];
if (ok) {
objectsToPaste = [pasteboard readObjectsForClasses:classArray options:options];
}
NSLog(#"objectsToPaste count = %li",[objectsToPaste count]);
return [objectsToPaste objectAtIndex:0];
}
I've noticed some strange behaviour that I will try to explain with an example:
Input
Cmd+C the string "A"
Cmd+C the string "B"
Cmd+C the string "C"
Cmd+C the string "D"
Output from determineAndAddToHistory
A
A
B
C
So I'm noticing that it retains that first item for some reason...then returns me the second most recent item each time. I've tried outputting the objectsToPaste array in the getClipboardDataNatively method, and this still is the case. Could someone please let me know how I would approach this problem, or how they've solved it?
P.S. my ClipboardUtilities class does not implement any Delegates, or inherit from anything but NSObject.
Well I guess since nobody likes long questions (I'll have to figure out how to shorten this), I figured something out. For some reason, I get the hotkey call really quickly (the clipboard gets updated AFTER the key is called in fact). As a result, I just have a small delay, and my model is now updated properly:
NSTimer* briefDelay = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(determineAndAddToHistory) userInfo:nil repeats:NO];
[[NSRunLoop mainRunLoop] addTimer:briefDelay forMode:NSRunLoopCommonModes];
I do not invalidate the timer manually, as per the documentation:
repeats
If YES, the timer will repeatedly reschedule itself until invalidated. If NO, the timer will be invalidated after it fires.

How to increment a 'score' during fast enumeration

I have the following code which I would like to use to check user answers and output a score (out of 5). I use a plist with the answers in and check the textField.text against it. What I'm struggling with is: how to get an output score as a total using this method?
- (IBAction)checkAnswers:(UITextField *)textField
{
NSString *path2 = [[NSBundle mainBundle] pathForResource:#"7A Cells Microscopes 3" ofType:#"plist"];
NSDictionary *dictionary = [[NSDictionary alloc] initWithContentsOfFile:path2];
NSString *tester = [NSString stringWithFormat:#"%i", textField.tag];
// NSDictionary *secondDict = [dictionary valueForKey:tester];
// NSString *answer = [secondDict valueForKey:#"Answer"];
// if ([textField.text isEqualToString:[[dictionary valueForKey:tester] valueForKey:#"Answer"]]) {
// NSLog(#"YAY");
// }
NSArray *allTextFields = [[NSArray alloc] initWithObjects:eyepiece, objectiveLens, focussingKnobs, stage, mirror, nil];
for (textField in allTextFields) {
int x = 0;
if ([textField.text isEqualToString:[[dictionary valueForKey:tester] valueForKey:#"Answer"]]) {
x++;
NSLog(#"%i", x);
}
}
Any help would be much appreciated!
Many thanks.
Assuming the rest of your code is good, just move int x = 0; outside the for loop. The way you have it coded x is reset to 0 on every loop... so it never counts.

NSTableView hit tab to jump from row to row while editing

I've got an NSTableView. While editing, if I hit tab it automatically jumps me to the next column. This is fantastic, but when I'm editing the field in the last column and I hit tab, I'd like focus to jump to the first column of the NEXT row.
Any suggestions?
Thanks to Michael for the starting code, it was very close to what ended up working! Here is the final code that I used, hope it will be helpful to someone else:
- (void) textDidEndEditing: (NSNotification *) notification {
NSInteger editedColumn = [self editedColumn];
NSInteger editedRow = [self editedRow];
NSInteger lastColumn = [[self tableColumns] count] - 1;
NSDictionary *userInfo = [notification userInfo];
int textMovement = [[userInfo valueForKey:#"NSTextMovement"] intValue];
[super textDidEndEditing: notification];
if ( (editedColumn == lastColumn)
&& (textMovement == NSTabTextMovement)
&& editedRow < ([self numberOfRows] - 1)
)
{
// the tab key was hit while in the last column,
// so go to the left most cell in the next row
[self selectRowIndexes:[NSIndexSet indexSetWithIndex:(editedRow+1)] byExtendingSelection:NO];
[self editColumn: 0 row: (editedRow + 1) withEvent: nil select: YES];
}
}
You can do this without subclassing by implementing control:textView:doCommandBySelector:
-(BOOL)control:(NSControl *)control textView:(NSTextView *)textView doCommandBySelector:(SEL)commandSelector {
if(commandSelector == #selector(insertTab:) ) {
// Do your thing
return YES;
} else {
return NO;
}
}
(The NSTableViewDelegate implements the NSControlTextEditingDelegate protocol, which is where this method is defined)
This method responds to the actual keypress, so you're not constrained to the textDidEndEditing method, which only works for text cells.
Subclass UITableView and add code to catch the textDidEndEditing call.
You can then decide what to do based on something like this:
- (void) textDidEndEditing: (NSNotification *) notification
{
NSDictionary *userInfo = [notification userInfo];
int textMovement = [[userInfo valueForKey:#"NSTextMovement"] intValue];
if ([self selectedColumn] == ([[self tableColumns] count] - 1))
(textMovement == NSTabTextMovement)
{
// the tab key was hit while in the last column,
// so go to the left most cell in the next row
[yourTableView editColumn: 0 row: ([self selectedRow] + 1) withEvent: nil select: YES];
}
[super textDidEndEditing: notification];
[[self window] makeFirstResponder:self];
} // textDidEndEditing
This code isn't tested... no warranties... etc. And you might need to move that [super textDidEndEditing:] call for the tab-in-the-right-most-cell case. But hopefully this will help you to the finish line. Let me know!

IPad - how to only type 0~9 on textfield?

Hello
I know ipad keyboard doesn't like iphone can set "UIKeyboardTypeNumberPad"!!
But if I wanna it only can type and show number 0 to 9 on textfield.
How to compare what user key in on textfield are numbers or not ??
Thank in advance.
Mini
instead of comparing a figure after it is displayed, do it in the shouldChangeCharactersInRange
be sure to declare the delegate UITextFieldDelegate, and something i always forget, make sure the delegate of the textField itself is pointing at the class that has the code in it.
//---------------------------------------------------------------------------
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
if ([string length] == 0 && range.length > 0)
{
textField.text = [textField.text stringByReplacingCharactersInRange:range withString:string];
return NO;
}
NSCharacterSet *nonNumberSet = [[NSCharacterSet characterSetWithCharactersInString:#"0123456789"] invertedSet];
if ([string stringByTrimmingCharactersInSet:nonNumberSet].length > 0)return YES;
return NO;
}
Take a look at this thread. It solved my similar problem.
How about How to dismiss keyboard for UITextView with return key??
The idea is you check every time the user hits a key, and if it is a number let it through. Otherwise ignore it.
Make your Controller supports the UITextViewDelegate protocol and implement the textView:shouldChangeTextInRange:replacementText: method.
(BOOL) textField: (UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString: (NSString *)string {
NSNumberFormatter * nf = [[NSNumberFormatter alloc] init];
[nf setNumberStyle:NSNumberFormatterNoStyle];
NSString * newString = [NSString stringWithFormat:#"%#%#",textField.text,string];
NSNumber * number = [nf numberFromString:newString];
if (number) {
return YES;
} else
return NO;
}

Resources