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。このコードではプレースホルダー テキストにa を使用している$0.00ため、テキストを nil に設定するとプレースホルダー テキストが表示されることに注意してください。

于 2013-11-21T20:43:34.400 に答える
1

これを処理するオープン ソースのUITextFieldサブクラスを作成しました。

https://github.com/TomSwift/TSCurrencyTextField

使い方はとても簡単で、UITextField の代わりにドロップするだけです。必要に応じて UITextFieldDelegate を提供することもできますが、これは必須ではありません。

于 2013-10-31T17:37:56.233 に答える