Riapplicare la formattazione della valuta a un UITextField su un evento di modifica

Sto lavorando con un UITextField che contiene un valore di valuta localizzato. Ho visto molti post su come lavorare con questo, ma la mia domanda è: come posso applicare nuovamente la formattazione della valuta a UITextField dopo ogni pressione di un tasto?

So che posso installare e utilizzare un formattatore di valuta con:

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

ma non so come collegarlo.

Ad esempio, se il valore nel campo indica “$ 12,345” e l’utente tocca la chiave “6”, il valore dovrebbe cambiare in “$ 123,456”.

Quale callback è quello “corretto” per farlo (dovrei usare textField:shouldChangeCharactersInRange:replacementString: o un target-action personalizzato) e come posso usare NSNumberFormatter per analizzare e ri-applicare la formattazione alla proprietà text di UITextField?

Qualsiasi aiuto sarebbe molto apprezzato! Grazie!

Ho lavorato su questo problema e penso di aver trovato una soluzione bella e pulita. Ti mostrerò come aggiornare in modo appropriato il campo di testo sull’input dell’utente, ma dovrai capire da solo la localizzazione, quella parte dovrebbe comunque essere abbastanza facile.

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

Questo è il codice nel metodo 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. } 

Modifica 1: perdita di memoria fissa.

Modifica 2: aggiornato per Umka – gestione di 0 dopo il separatore decimale. Il codice viene aggiornato anche per ARC.

Ecco la mia versione di questo.

Imposta un formattatore in qualche posto:

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

Quindi utilizzalo per analizzare e formattare 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; } 

Questa era una buona base, ma non soddisfaceva ancora le mie esigenze di app. Ecco cosa ho cucinato per aiutarlo. Capisco che il codice è in realtà ingombrante ed è qui piuttosto per dare un indizio e potrebbe essere quello di ottenere una soluzione più elegante.

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

Il formattatore di numeri è inizializzato in un modo come questo:

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

È localizzato, quindi dovrebbe funzionare bene in diverse impostazioni locali. Supporta anche i seguenti input (esempi):

0,00 0,01

1,333,333.03

Per favore qualcuno lo migliori. L’argomento è un po ‘interessante, nessuna soluzione elegante esiste finora (iOS non ha cosa setFormat ()).

Per la semplice immissione di valuta in stile ATM, utilizzo il seguente codice, che funziona piuttosto bene, se si utilizza l’opzione UIKeyboardTypeNumberPad , che consente solo cifre e UIKeyboardTypeNumberPad .

Per prima cosa imposta un NSNumberFormatter questo modo:

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

Quindi, utilizzare il seguente codice 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; } 

La maggior parte del lavoro viene eseguita nell’impostazione del valore newText ; la modifica proposta viene utilizzata ma tutti i caratteri non numerici inseriti da NSNumberFormatter vengono eliminati.

Quindi il primo se test verifica se il testo è vuoto o tutti zeri (messi lì dall’utente o dal formattatore). In caso contrario, il prossimo test fa in modo che non venga accettato altro input tranne i backspaces se il numero è già al numero massimo di cifre.

L’ultima parte rivede il numero usando il formattatore in modo che l’utente veda sempre qualcosa come $1,234.50 . Nota che uso $0.00 per il testo segnaposto con questo codice, quindi l’impostazione del testo su nil mostra il testo segnaposto.

Ho scritto una sottoclass UITextField open source per gestire questo, disponibile qui:

https://github.com/TomSwift/TSCurrencyTextField

È molto facile da usare: basta rilasciarlo al posto di qualsiasi UITextField. Puoi comunque fornire UITextFieldDelegate se lo desideri, ma non è necessario.

Questa risposta si basa un po ‘sul codice di Michael Klosson per correggere alcuni bug di valuta regionali, alcune posizioni hanno causato il suo codice di sputare sempre un valore 0 nella conversione finale in stringa textField. Ho anche suddiviso il nuovo assetto di testo per rendere più facile per alcuni di noi i più recenti programmatori di digerire, a volte il nesting è per me fastidioso. Ho disconnesso tutte le opzioni di posizione NSLocale e il loro valore maximumFractionDigits quando un NSNUmberFormatter è impostato su di esse. Ho trovato che c’erano solo 3 possibilità. La maggior parte usa 2, altri ne usa nessuno e alcuni ne usano 3. Questo metodo delegato per il campo di testo gestisce tutte e 3 le possibilità, per garantire un formato corretto in base alla regione.

Ecco il mio metodo del delegato textField, principalmente solo una riscrittura del codice di Michael, ma con l’assegnazione del nuovo testo ampliata, il supporto decimale regionale aggiunto e l’uso di NSDecimalNumbers come quello che sto memorizzando nel mio modello di dati di base comunque.

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