9

我正在使用一个包含本地化货币值的 UITextField。我看过很多关于如何使用它的帖子,但我的问题是:如何在每次按键后将货币格式重新应用到 UITextField?

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

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

但我不知道如何连接它。

例如,如果字段中的值显示为“$12,345”并且用户点击“6”键,则该值应更改为“$123,456”。

哪个回调是“正确”的回调(我应该使用textField:shouldChangeCharactersInRange:replacementString:还是自定义目标操作)以及如何使用 NSNumberFormatter 解析并将格式重新应用到 UITextField 的文本属性?

任何帮助将非常感激!谢谢!

4

6 回答 6

9

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

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

- (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 的代码也更新了。

于 2010-05-27T08:27:37.633 回答
4

这是我的版本。

在某个地方设置格式化程序:

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

然后使用它来解析和格式化 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;
}
于 2012-09-06T04:40:27.273 回答
4

这是很好的基础,但仍然不能满足我的应用需求。这是我做的帮助它。我知道代码实际上很麻烦,它在这里而不是提供线索,可能是为了获得更优雅的解决方案。

- (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;

它是语言环境感知的,因此应该在不同的语言环境设置中正常工作。它还支持以下输入(示例):

0.00 0.01

1,333,333.03

请有人改进这一点。话题有点意思,目前还没有优雅的解决方案(iOS 没有 setFormat() 的东西)。

于 2012-07-15T16:48:22.913 回答
2

对于简单的 ATM 式货币输入,我使用以下代码,如果您使用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值时完成的;使用了建议的更改,但删除了所有输入的非数字字符NSNumberFormatter

然后第一个 if 测试检查文本是否为空或全为零(由用户或格式化程序放在那里)。如果不是,则下一个测试确保如果数字已经达到最大位数,则除退格之外不再接受其他输入。

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

于 2013-11-21T20:43:34.400 回答
1

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

https://github.com/TomSwift/TSCurrencyTextField

它非常易于使用 - 只需将其放在任何 UITextField 的位置即可。如果您愿意,您仍然可以提供 UITextFieldDelegate,但这不是必需的。

于 2013-10-31T17:37:56.233 回答
0

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

这是我的 textField 委托方法,主要只是重写了 Michael 的代码,但是随着 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;
    }
于 2013-12-05T16:56:47.023 回答