将货币格式重新应用于更改事件的UITextField

我正在处理包含本地货币值的UITextField。 我已经看到了很多关于如何处理这个问题的post,但是我的问题是:如何在每次按键之后重新将货币格式应用到UITextField?

我知道我可以设置和使用货币格式化程序:

NSNumberFormatter *currencyFormatter = [[NSNumberFormatter alloc] init]; [currencyFormatter setNumberStyle:NSNumberFormatterCurrencyStyle]; ... [currencyFormatter stringFromNumber:...]; 

但我不知道如何挂钩。

例如,如果该字段中的值为“$ 12,345”,而用户点击“6”键,则该值将变为“$ 123,456”。

哪个callback是“正确的”(我应该使用textField:shouldChangeCharactersInRange:replacementString:还是一个自定义的target-action),以及如何使用NSNumberFormatter来parsing和重新应用格式到UITextField的文本属性?

任何帮助将不胜感激! 谢谢!

我一直在研究这个问题,我想我找出了一个不错的,干净的解决scheme。 我将向您展示如何适当地更新用户input的文本字段,但是您必须自己弄清楚本地化,无论如何这个部分应该很简单。

 - (void)viewDidLoad { [super viewDidLoad]; // setup text field ... #define PADDING 10.0f const CGRect bounds = self.view.bounds; CGFloat width = bounds.size.width - (PADDING * 2); CGFloat height = 30.0f; CGRect frame = CGRectMake(PADDING, PADDING, width, height); self.textField = [[UITextField alloc] initWithFrame:frame]; _textField.backgroundColor = [UIColor whiteColor]; _textField.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter; _textField.autocapitalizationType = UITextAutocapitalizationTypeNone; _textField.autocorrectionType = UITextAutocorrectionTypeNo; _textField.text = @"0"; _textField.delegate = self; [self.view addSubview:_textField]; } - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; // force update for text field, so the initial '0' will be formatted as currency ... [self textField:_textField shouldChangeCharactersInRange:NSMakeRange(0, 0) replacementString:@"0"]; } - (void)viewDidUnload { self.textField = nil; [super viewDidUnload]; } 

这是UITextFieldDelegate方法textField:shouldChangeCharactersInRange:replacementString:的代码textField:shouldChangeCharactersInRange:replacementString:

 - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string { NSString *text = _textField.text; NSString *decimalSeperator = @"."; NSCharacterSet *charSet = nil; NSString *numberChars = @"0123456789"; // the number formatter will only be instantiated once ... static NSNumberFormatter *numberFormatter; if (!numberFormatter) { numberFormatter = [[NSNumberFormatter alloc] init]; numberFormatter.numberStyle = NSNumberFormatterCurrencyStyle; numberFormatter.maximumFractionDigits = 10; numberFormatter.minimumFractionDigits = 0; numberFormatter.decimalSeparator = decimalSeperator; numberFormatter.usesGroupingSeparator = NO; } // create a character set of valid chars (numbers and optionally a decimal sign) ... NSRange decimalRange = [text rangeOfString:decimalSeperator]; BOOL isDecimalNumber = (decimalRange.location != NSNotFound); if (isDecimalNumber) { charSet = [NSCharacterSet characterSetWithCharactersInString:numberChars]; } else { numberChars = [numberChars stringByAppendingString:decimalSeperator]; charSet = [NSCharacterSet characterSetWithCharactersInString:numberChars]; } // remove amy characters from the string that are not a number or decimal sign ... NSCharacterSet *invertedCharSet = [charSet invertedSet]; NSString *trimmedString = [string stringByTrimmingCharactersInSet:invertedCharSet]; text = [text stringByReplacingCharactersInRange:range withString:trimmedString]; // whenever a decimalSeperator is entered, we'll just update the textField. // whenever other chars are entered, we'll calculate the new number and update the textField accordingly. if ([string isEqualToString:decimalSeperator] == YES) { textField.text = text; } else { NSNumber *number = [numberFormatter numberFromString:text]; if (number == nil) { number = [NSNumber numberWithInt:0]; } textField.text = isDecimalNumber ? text : [numberFormatter stringFromNumber:number]; } return NO; // we return NO because we have manually edited the textField contents. } 

编辑1:修复内存泄漏。

编辑2:更新为Umka – 处理小数点后的0 。 代码也更新为ARC。

这是我的这个版本。

设置一个格式化器的地方:

  // Custom initialization formatter = [NSNumberFormatter new]; [formatter setNumberStyle: NSNumberFormatterCurrencyStyle]; [formatter setLenient:YES]; [formatter setGeneratesDecimalNumbers:YES]; 

然后用它来parsing和格式化UITextField:

 -(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string { NSString *replaced = [textField.text stringByReplacingCharactersInRange:range withString:string]; NSDecimalNumber *amount = (NSDecimalNumber*) [formatter numberFromString:replaced]; if (amount == nil) { // Something screwed up the parsing. Probably an alpha character. return NO; } // If the field is empty (the inital case) the number should be shifted to // start in the right most decimal place. short powerOf10 = 0; if ([textField.text isEqualToString:@""]) { powerOf10 = -formatter.maximumFractionDigits; } // If the edit point is to the right of the decimal point we need to do // some shifting. else if (range.location + formatter.maximumFractionDigits >= textField.text.length) { // If there's a range of text selected, it'll delete part of the number // so shift it back to the right. if (range.length) { powerOf10 = -range.length; } // Otherwise they're adding this many characters so shift left. else { powerOf10 = [string length]; } } amount = [amount decimalNumberByMultiplyingByPowerOf10:powerOf10]; // Replace the value and then cancel this change. textField.text = [formatter stringFromNumber:amount]; return NO; } 

这是很好的基础,但仍然没有满足我的应用程序的需求。 这是我烹饪来帮助它。 我明白,代码实际上是麻烦的,而且在这里给出一个线索,可能会得到更优雅的解决scheme。

 - (BOOL)textField:(UITextField *)aTextField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string { if (aTextField == myAmountTextField) { NSString *text = aTextField.text; NSString *decimalSeperator = [[NSLocale currentLocale] objectForKey:NSLocaleDecimalSeparator]; NSString *groupSeperator = [[NSLocale currentLocale] objectForKey:NSLocaleGroupingSeparator]; NSCharacterSet *characterSet = nil; NSString *numberChars = @"0123456789"; if ([text rangeOfString:decimalSeperator].location != NSNotFound) characterSet = [NSCharacterSet characterSetWithCharactersInString:numberChars]; else characterSet = [NSCharacterSet characterSetWithCharactersInString:[numberChars stringByAppendingString:decimalSeperator]]; NSCharacterSet *invertedCharSet = [characterSet invertedSet]; NSString *trimmedString = [string stringByTrimmingCharactersInSet:invertedCharSet]; text = [text stringByReplacingCharactersInRange:range withString:trimmedString]; if ([string isEqualToString:decimalSeperator] == YES || [text rangeOfString:decimalSeperator].location == text.length - 1) { [aTextField setText:text]; } else { /* Remove group separator taken from locale */ text = [text stringByReplacingOccurrencesOfString:groupSeperator withString:@""]; /* Due to some reason, even if group separator is ".", number formatter puts spaces instead. Lets handle this. This all should be done before converting to NSNUmber as otherwise we will have nil. */ text = [text stringByReplacingOccurrencesOfString:@" " withString:@""]; NSNumber *number = [numberFormatter numberFromString:text]; if (number == nil) { [textField setText:@""]; } else { /* Here is what I call "evil miracles" is going on :) Totally not elegant but I did not find another way. This is all needed to support inputs like "0.01" and "1.00" */ NSString *tail = [NSString stringWithFormat:@"%@00", decimalSeperator]; if ([text rangeOfString:tail].location != NSNotFound) { [numberFormatter setPositiveFormat:@"#,###,##0.00"]; } else { tail = [NSString stringWithFormat:@"%@0", decimalSeperator]; if ([text rangeOfString:tail].location != NSNotFound) { [numberFormatter setPositiveFormat:@"#,###,##0.0#"]; } else { [numberFormatter setPositiveFormat:@"#,###,##0.##"]; } } text = [numberFormatter stringFromNumber:number]; [textField setText:text]; } } return NO; } return YES; } 

数字格式化程序以这种方式初始化:

 numberFormatter = [[NSNumberFormatter alloc] init]; [numberFormatter setNumberStyle:NSNumberFormatterDecimalStyle]; numberFormatter.roundingMode = kCFNumberFormatterRoundFloor; 

它是locale意识到的,所以应该在不同的区域设置中正常工作。 它还支持以下input(示例):

0.00 0.01

1,333,333.03

请有人改善这一点。 主题是有趣的,到目前为止还没有优雅的解决scheme(iOS没有setFormat()的东西)。

对于简单的ATM风格货币input, 如果使用仅允许数字和退格的UIKeyboardTypeNumberPad选项, 使用以下代码,这很有效。

首先设置一个像这样的NSNumberFormatter

 NSNumberFormatter* currencyFormatter = [[NSNumberFormatter alloc] init]; [currencyFormatter setNumberStyle:NSNumberFormatterCurrencyStyle]; [currencyFormatter setMaximumSignificantDigits:9]; // max number of digits including ones after the decimal here 

然后,使用下面的shouldChangeCharactersInRange代码:

 - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string { NSString* newText = [[[[textField.text stringByReplacingCharactersInRange:range withString:string] stringByReplacingOccurrencesOfString:currencyFormatter.currencySymbol withString:[NSString string]] stringByReplacingOccurrencesOfString:currencyFormatter.groupingSeparator withString:[NSString string]] stringByReplacingOccurrencesOfString:currencyFormatter.decimalSeparator withString:[NSString string]]; NSInteger newValue = [newText integerValue]; if ([newText length] == 0 || newValue == 0) textField.text = nil; else if ([newText length] > currencyFormatter.maximumSignificantDigits) textField.text = textField.text; else textField.text = [currencyFormatter stringFromNumber:[NSNumber numberWithDouble:newValue / 100.0]]; return NO; } 

大部分工作都是在设置newText值时完成的; build议的更改将被使用,但NSNumberFormatter中的所有非数字字符将被取出。

然后,第一个testing检查文本是否为空或全部为零(由用户或格式化程序放在那里)。 如果不是,则下一次testing确保如果该数字已经是最大数字位数,则将不接受除退格键外的其他input。

最后的else使用格式化程序重新显示数字,所以用户总是看到类似$1,234.50东西。 请注意,此代码占位符文本使用$0.00 ,因此将文本设置为零将显示占位符文本。

我写了一个开源的UITextField子类来处理这个问题,可以在这里find:

https://github.com/TomSwift/TSCurrencyTextField

使用起来非常简单 – 只需将它放在任何UITextField的位置即可。 如果你喜欢,你仍然可以提供一个UITextFieldDelegate,但这不是必需的。

这个答案在Michael Klosson的代码上修正了一些区域货币错误,有些位置导致他的代码在最终转换回textFieldstring时始终吐出一个0值。 我也分解了新的文本分配,使我们中的一些新编程人员更容易消化,嵌套对我来说有时候是令人厌烦的。 我注销了所有的NSLocale位置选项和它们的maximumFractionDigits值时,NSNUmberFormatter设置为他们。 我发现只有3种可能性。 大多数使用2,其他一些使用none,一些使用3.这个文本字段委托方法处理所有3种可能性,以确保基于区域的正确格式。

这是我的textField委托方法,主要是重写迈克尔的代码,但扩展了newText分配,添加了区域十进制支持,并使用NSDecimalNumbers作为我在核心数据模型中存储的数据。

 - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string { // get the format symbols NSNumberFormatter *currencyFormatter = self.currencyFormatter; NSString *currencySymbol = currencyFormatter.currencySymbol; NSString *groupSeperator = currencyFormatter.groupingSeparator; NSString *decimalSeperator = currencyFormatter.decimalSeparator; // strip all the format symbols to leave an integer representation NSString* newText = [textField.text stringByReplacingCharactersInRange:range withString:string]; newText = [newText stringByReplacingOccurrencesOfString:currencySymbol withString:@""]; newText = [newText stringByReplacingOccurrencesOfString:groupSeperator withString:@""]; newText = [newText stringByReplacingOccurrencesOfString:decimalSeperator withString:@""]; NSDecimalNumber *newValue = [NSDecimalNumber decimalNumberWithString:newText]; if ([newText length] == 0 || newValue == 0) textField.text = nil; else if ([newText length] > currencyFormatter.maximumSignificantDigits) { // NSLog(@"We've maxed out the digits"); textField.text = textField.text; } else { // update to new value NSDecimalNumber *divisor; // we'll need a number to divide by if local currency uses fractions switch (currencyFormatter.maximumFractionDigits) { // the max fraction digits tells us the number of decimal places // divide accordingly based on the 3 available options case 0: // NSLog(@"No Decimals"); divisor = [NSDecimalNumber decimalNumberWithString:@"1.0"]; break; case 2: // NSLog(@"2 Decimals"); divisor = [NSDecimalNumber decimalNumberWithString:@"100.0"]; break; case 3: // NSLog(@"3 Decimals"); divisor = [NSDecimalNumber decimalNumberWithString:@"1000.0"]; break; default: break; } NSDecimalNumber *newAmount = [newValue decimalNumberByDividingBy:divisor]; textField.text = [currencyFormatter stringFromNumber:newAmount]; } return NO; }