Как лучше всего проверить ввод валюты в UITextField?

Мое приложение позволяет пользователю вводить числовое значение (валюту) в элементе управления UITextField, но желаемая раскладка клавиатуры, к сожалению, не является одной из встроенных опций, поэтому мне пришлось выбрать опцию «Числа и пунктуация» в Интерфейсном Разработчике. Вот соответствующее диалоговое окно в IB:

введите описание изображения здесь

Поэтому, когда мое приложение запрашивает у пользователя ввод, оно отображает следующее:

введите описание изображения здесь

Это прекрасно, но посмотрите на все дополнительные ключи, доступные пользователю! Можно было легко ввести "12; 56!" в текстовом поле, и я предполагаю, что мне нужно как-то это проверить.

Итак, мой вопрос: как мне проверить значения валюты, введенные в UITextField?


person jpm    schedule 04.12.2008    source источник


Ответы (13)


Возможно, вы могли бы прикрепить UITextFieldDelegate к элементу управления и заставить его реализовать textField:shouldChangeCharactersInRange:replacementString:. Таким образом, если он увидит какие-либо символы, которые вам не нужны в поле, он сможет их отклонить. .

person Marc Novakowski    schedule 04.12.2008

У меня есть желание ответить, потому что это была первая запись, которую я увидел, когда я погуглил, и ответ с самым высоким рейтингом не позволил мне ввести валюту.
Я немец, и я из Германии (и во многих других странах) мы используем , в качестве десятичного разделителя.

Я только что написал похожий метод, и это то, что у меня есть прямо сейчас.

- (BOOL) textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
    static NSString *numbers = @"0123456789";
    static NSString *numbersPeriod = @"01234567890.";
    static NSString *numbersComma = @"0123456789,";

    //NSLog(@"%d %d %@", range.location, range.length, string);
    if (range.length > 0 && [string length] == 0) {
        // enable delete
        return YES;
    }

    NSString *symbol = [[NSLocale currentLocale] objectForKey:NSLocaleDecimalSeparator];
    if (range.location == 0 && [string isEqualToString:symbol]) {
        // decimalseparator should not be first
        return NO;
    }
    NSCharacterSet *characterSet;
    NSRange separatorRange = [textField.text rangeOfString:symbol];
    if (separatorRange.location == NSNotFound) {
        if ([symbol isEqualToString:@"."]) {
            characterSet = [[NSCharacterSet characterSetWithCharactersInString:numbersPeriod] invertedSet];
        }
        else {
            characterSet = [[NSCharacterSet characterSetWithCharactersInString:numbersComma] invertedSet];              
        }
    }
    else {
        // allow 2 characters after the decimal separator
        if (range.location > (separatorRange.location + 2)) {
            return NO;
        }
        characterSet = [[NSCharacterSet characterSetWithCharactersInString:numbers] invertedSet];               
    }
    return ([[string stringByTrimmingCharactersInSet:characterSet] length] > 0);
}
person Matthias Bauch    schedule 31.01.2011
comment
Это здорово, спасибо. Только что у клиента был плач из-за более чем двух знаков после запятой :) - person tomwilson; 16.08.2011
comment
Это, безусловно, более комплексное решение. Я предлагаю сделать это принятым ответом. - person Ceryni; 20.06.2015

Хотя вы можете возиться с NSNumberFormatter, мне было проще просто отсеивать все, кроме 0-9 и.

У меня это хорошо работает:


- (BOOL) textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
    NSCharacterSet *nonNumberSet = [[NSCharacterSet characterSetWithCharactersInString:@"0123456789."] invertedSet];

return ([string stringByTrimmingCharactersInSet:nonNumberSet].length > 0);

}

person Community    schedule 19.06.2009
comment
как насчет отрицательных чисел? разве вы не хотите добавить - к действительному набору, который вы инвертируете? - person Erich Mirabal; 19.06.2009
comment
как насчет того, если пользователь хочет удалить введенные цифры? - person ashish; 01.12.2010
comment
Приведенный выше код работает, за исключением того, что клавиша Backspace также отключена (как упоминалось в ashish) - person topace; 12.10.2011

Я обнаружил, что это относительно чистый подход. Я не тестировал его с валютами, отличными от США, но, поскольку он использует свойства NSNumberFormatter, я считаю, что он должен обрабатывать их правильно.

Начните с настройки форматтера в каком-нибудь месте:

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

Используйте средство форматирования для анализа и переформатирования их ввода. Он также обрабатывает сдвиг числа при добавлении и удалении цифр.

-(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 initial 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;
}
person drewish    schedule 06.09.2012
comment
Это действительно рабочий подход. В большинстве случаев работает как амулет. - person Eugene Prokoshev; 22.05.2014
comment
Что происходит, когда символ валюты находится сзади? - person super9; 11.06.2014

В сочетании с предложением textField:shouldChangeCharactersInRange:replacementString:, сделанным Марком, вы должны передать текст через NSNumberFormatter, используя NSNumberFormatterCurrencyStyle. Это позволит справиться с особенностями форматирования валюты и обработать параметры, специфичные для локали.

Если вы его найдете, в документации iPhone есть раздел «Руководство по программированию форматирования данных для какао». К сожалению, большая часть информации о пользовательском интерфейсе здесь предназначена для Mac OS X (не работает на iPhone), но она покажет вам, как использовать классы форматирования.

person Matt Gallagher    schedule 04.12.2008

Я обнаружил, что shouldChangeCharactersInRange также закручивает всплывающую клавиатуру, возврат и кнопку «Готово». Я обнаружил, что если я обрабатывал строки нулевой длины и разрешал управляющие символы, все работало нормально.

Мне не нравится использовать NSNumberFormatter, потому что он настаивает на том, чтобы число было правильно сформировано на всех этапах, пока пользователь редактирует, и это может привести в ярость, если вы, скажем, хотите иметь две десятичные точки в номере на мгновение, пока вы не удалите тот, что не в том месте.

Вот код, который я использовал:

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
    if ([string length] < 1)    // non-visible characters are okay
        return YES;
    if ([string stringByTrimmingCharactersInSet:[NSCharacterSet controlCharacterSet]].length == 0)
        return YES;
    return ([string stringByTrimmingCharactersInSet:[self.characterSet invertedSet]].length > 0);
}

Где self.characterSet содержит допустимые символы, я использовал этот метод для создания его для валюты:

- (NSCharacterSet *)createCurrencyCharacterSet
{
    NSLocale *locale = [NSLocale currentLocale];
    NSMutableCharacterSet *currencySet = [NSMutableCharacterSet decimalDigitCharacterSet];

    [currencySet addCharactersInString:@"-"];       // negative symbol, can't find a localised version
    [currencySet addCharactersInString:[locale objectForKey:NSLocaleCurrencySymbol]];
    [currencySet addCharactersInString:[locale objectForKey:NSLocaleGroupingSeparator]];
    [currencySet addCharactersInString:[locale objectForKey:NSLocaleDecimalSeparator]];

    return [[currencySet copy] autorelease];
}

Несколько неудачный код [[currencySet copy] autorelease] возвращает неизменный NSCharacterSet.

Использование [NSCharacterSet decimalDigitCharacterSet] также включает в себя индийские и арабские эквивалентные символы, что, надеюсь, означает, что люди, использующие эти языки, могут использовать цифры своего алфавита для ввода чисел.

По-прежнему необходимо проверить, может ли NSNumberFormatter анализировать ввод пользователя и предупреждать, если он не может; тем не менее, когда можно вводить только допустимые символы, это дает больше удовольствия.

person Ben Golding    schedule 10.02.2011

Я все время вижу, что люди не знают, как правильно определить результирующую строку при реализации shouldChangeCharactersInRange.

Вот как вы получите новый текст, который будет введен, если вы ответите ДА:

NSString *newText = [textField.text stringByReplacingCharactersInRange:range withString:string];

Когда у вас есть этот новый текст, легко проверить, является ли этот текст допустимым числом или нет, и вернуть ДА или НЕТ соответственно.

Имейте в виду, что все другие решения, такие как показанные здесь, где проверяется длина «строки» и т. Д., Могут работать некорректно, если пользователь пытается отредактировать строку с помощью клавиатуры Bluetooth с клавишами управления курсором или с помощью дополнительных функции редактирования (выбрать, вырезать, вставить).

person Thomas Tempelmann    schedule 11.05.2011
comment
Я искал в Google только эту проблему, и каким-то образом Google ее нашел. Престижность. - person David M.; 13.04.2012

Если вы хотите, чтобы они могли вводить только цифры - вы можете также рассмотреть цифровую клавиатуру.

person Grouchal    schedule 19.06.2009
comment
Не поможет, если пользователь подключил bluetooth-клавиатуру, так как тогда он может вводить то, что ему нравится. - person Thomas Tempelmann; 12.05.2011

Найдя быстрое решение проблемы переполнения стека для работы с валютой США, я переписал функцию для безопасной работы с международными валютами. Это решение будет динамически проверять ввод данных пользователем из UITextField, корректируя их по мере ввода.

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {      

   NSNumberFormatter *nf = [[NSNumberFormatter alloc] init];

   [numberFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4];
   [numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
   [numberFormatter setMaximumFractionDigits:2];
   [numberFormatter setMinimumFractionDigits:0];

    // Grab the contents of the text field
    NSString *text = [textField text];  

    // the appropriate decimalSeperator and currencySymbol for the current locale
    // can be found with help of the
    // NSNumberFormatter and NSLocale classes.
    NSString *decimalSeperator = numberFormatter.decimalSeparator;  
    NSString *currencySymbol = numberFormatter.currencySymbol;

    NSString *replacementText = [text stringByReplacingCharactersInRange:range withString:string];
    NSMutableString *newReplacement = [[ NSMutableString alloc ] initWithString:replacementText];

    // whenever a decimalSeperator or currencySymobol 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 the number can't be computed, we ignore the new input.
    NSRange decimalRange = [text rangeOfString:decimalSeperator];
    if ([string isEqualToString:decimalSeperator] == YES && 
        [text rangeOfString:decimalSeperator].length == 0) {
        [textField setText:newReplacement];
    } else if([string isEqualToString:currencySymbol] == YES&& 
              [text rangeOfString:currencySymbol].length == 0) {
        [textField setText:newReplacement];        
    } else if([newReplacement isEqualToString:currencySymbol] == YES) {
        return YES;
    }else {

        NSString *currencyGroupingSeparator = numberFormatter.currencyGroupingSeparator;

        [newReplacement replaceOccurrencesOfString:currencyGroupingSeparator withString:@"" options:NSBackwardsSearch range:NSMakeRange(0, [newReplacement length])];        
        [numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];

        NSNumber *number = [numberFormatter numberFromString:newReplacement];

        if([newReplacement length] == 1) {
            [numberFormatter setNumberStyle:NSNumberFormatterDecimalStyle];
            number = [numberFormatter numberFromString:newReplacement];
        }

        if (number == nil) { 
            [newReplacement release];
            return NO;
        }
        [numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];    
        text = [numberFormatter stringFromNumber:number];
        [textField setText:text];       
    }

    [newReplacement release];

    return NO; // we return NO because we have manually edited the textField contents.
}
person Brian L    schedule 22.10.2011
comment
спасибо Брайан, вы заметили, что в вашем коде вы не можете ввести 0 после десятичной дроби - person zambono; 26.12.2011

Использование NSNumberFormatter - правильный ответ. Он будет обрабатывать проверку и преобразовывать строку в правильный тип объекта и обратно.

person Marc Charbonneau    schedule 05.12.2008

Вы также можете использовать решение Gamma-Point и оценить, равно ли * aNumber Nil, если его Nil, то это означает символ, который не был 0-9. или, было введено, таким образом вы можете проверять только числа, он обнулит переменную, если введен какой-либо символ без числа.

person Aku    schedule 21.10.2010

Не удалось найти подходящую реализацию для проверки валюты. Вот мой вариант:

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {

   NSString* proposedString = [textField.text stringByReplacingCharactersInRange:range withString:string];

   //if there is empty string return YES
   if([proposedString length] == 0) {
       return YES;
   }

   //create inverted set for appripriate symbols
   NSCharacterSet *nonNumberSet = [[NSCharacterSet characterSetWithCharactersInString:@"0123456789.,"] invertedSet];

   //if side symbols is trimmed by nonNumberSet - return NO
   if([proposedString stringByTrimmingCharactersInSet:nonNumberSet].length != [proposedString length]) {
       return NO;
   }

   //if there is more than 1 symbol of '.' or ',' return NO
   if([[proposedString componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@".,"]] count] > 2) {
      return NO;
   }

   //finally check is ok, return YES
   return YES;
}
person supp-f    schedule 17.01.2012

Использование shouldChangeCharactersInRange портит всплывающую клавиатуру, поскольку кнопка возврата не работает.

Средство форматирования чисел - это то, что вам нужно. Вот образец, который я использовал, чтобы найти положительные десятичные дроби. Я называю это во время проверки - например, когда пользователь нажимает кнопку сохранения.

    -(BOOL) isPositiveNumber: (NSString *) numberString {

    NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
    [numberFormatter setNumberStyle:NSNumberFormatterDecimalStyle];

    NSNumber *aNumber  =  [numberFormatter numberFromString:numberString];
    [numberFormatter release];

    if ([aNumber floatValue] > 0) {
        NSLog( @"Found positive number %4.2f",[aNumber floatValue] );

        return YES;
    }
    else {
        return NO;
    }
}
person Gamma-Point    schedule 04.09.2010
comment
Вы можете обнаружить обратное пространство, так как диапазон отрицательный. - person drewish; 23.07.2012
comment
Проблема с этим кодом в том, что вы не можете удалить значение. Введите 1 и попробуйте вернуть его на место. Пустая строка приведет к ошибке numberFromString, сделав aNumber nil, а [nil floatValue] даст 0, что означает, что она не примет изменение. - person drewish; 23.07.2012