6

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?

Community
  • 1
  • 1
Meltemi
  • 37,979
  • 50
  • 195
  • 293

7 Answers7

10

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;
}
Lars Schneider
  • 5,530
  • 4
  • 33
  • 58
3

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.

  1. Create a UITextField, make it hidden, assign it a delegate, and then make it the first responder to summon the keyboard.
  2. 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.
  3. 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!

benzado
  • 82,288
  • 22
  • 110
  • 138
  • This is really frustrating as it seems like a lot of hoops just to accept a currency amount? Isn't there a simpler way? Shouldn't the NSNumberFormatter be able to handle accepting digits and displaying them as currency... and the opposite of converting displayed currency to an NSNumber for storage? – Meltemi Mar 04 '09 at 01:24
  • That procedure isn't that hard. The Cocoa equivalent is 10–20 lines. – Peter Hosey Mar 04 '09 at 05:12
  • 1
    UITextField does not have support for formatters like NSTextField (on Mac OS X) does. Sorry if that means your job is not easy. – benzado Mar 04 '09 at 19:52
  • If it's not obvious, I'm still puzzled. All I'm looking for is a link to some sample code (do you remember learning Cocoa?). But thanks for making me feel like even more of an idiot and for the down vote because it's so clear to newcomers that this is the ONLY forum w/out chronological threading. – Meltemi Mar 04 '09 at 22:22
  • For example, the closest thing I can find to step #2 in your response is UITextFieldTextDidChangeNotification in UITextField Class Reference. But I find little/no documentation on that. – Meltemi Mar 04 '09 at 22:43
  • You might find it helpful to read the desktop Cocoa documentation, since a lot of it applies to Cocoa Touch. Read about delegates, specifically. Have some patience, there won't always be sample code to cut and paste for everything you want to do. – benzado Mar 05 '09 at 06:03
1

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 :)

1

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.

DrFloyd5
  • 13,555
  • 3
  • 25
  • 34
0

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. :)

JonLim
  • 1,393
  • 2
  • 14
  • 23
0

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;

}

pedro
  • 1
  • 1
    You should use `localizedStringWithFormat:` to show the correct decimal separator. Does this solution handle non-fractional currencies (e.g., the Japanese yen) correctly? Does it handle currencies without a ¹⁄₁₀₀th unit correctly? – Peter Hosey Dec 18 '09 at 03:45
0

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.

TomSwift
  • 39,369
  • 12
  • 121
  • 149