31

I have a UITextField that when clicked brings up a number pad with a decimal point in the bottom left. I am trying to limit the field so that a user can only place 1 decimal mark

e.g.
2.5 OK
2..5 NOT OK

lnafziger
  • 25,572
  • 8
  • 58
  • 99
user1282180
  • 1,093
  • 3
  • 11
  • 18

16 Answers16

43

Implement the shouldChangeCharactersInRange method like this:

// Only allow one decimal point
// Example assumes ARC - Implement proper memory management if not using.
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string 
{
    NSString *newString = [textField.text stringByReplacingCharactersInRange:range withString:string];
    NSArray  *arrayOfString = [newString componentsSeparatedByString:@"."];

    if ([arrayOfString count] > 2 ) 
        return NO;

    return YES;
}

This creates an array of strings split by the decimal point, so if there is more than one decimal point we will have at least 3 elements in the array.

lnafziger
  • 25,572
  • 8
  • 58
  • 99
  • 1
    Rather than using componentsSeparatedByString, you could use a NSRegularExpression to determine whether the newString contains more than one decimal. – Josh Brown Jun 13 '12 at 14:21
  • 1
    True! There are lots of ways to do this. I generally use this because often I want to do something different with the integer and decimal portion. – lnafziger Jun 13 '12 at 19:36
  • Using a regular expression gives you better control of the characters the textfield accept and the format they will take. – nizx Jan 14 '14 at 03:01
  • 1
    @nizx For this specific case I don't think that it really matters much, although your example is longer and "looks" more complicated to a new user. For more complicated examples, you may very well be right, or if they are numbers then using a NSNumberFormatter may also be appropriate. – lnafziger Jan 14 '14 at 03:02
  • 9
    Instead of @"." for the decimal separator one could also get it from [[NSLocale currentLocale] objectForKey:NSLocaleDecimalSeparator]]. This way the code will also work correctly for regions using a comma as separator. – Frank Feb 15 '14 at 21:42
  • @Frank Very true, however the question specifically asked about "decimal points" and I don't want to cloud the issue. :) – lnafziger Feb 17 '14 at 20:15
  • @Inafziger In the Netherlands the keypad will show a comma on the decimal pad, not a dot. Hence using a hardcoded '.' would not give the wanted/expected results – Frank Feb 17 '14 at 21:58
  • @Frank Oh, I get that and it's a very valid point (and in fact what I do) but this question was specifically about a decimal point, and not the decimal seperator. :) – lnafziger Feb 17 '14 at 22:01
  • Also, by adding another condition to the 'if' statement, making it ` if ([arrayOfString count] > 2 || ([arrayOfString count] == 2 && [arrayOfString.lastObject length] > 2)) ` one could limit the number of chars after the decimal point as well (in this case **no more than 2** characters allowed) – Islam Q. Jan 04 '15 at 03:33
15

Here is an example with a regular expression, the example limits to only one decimal point and 2 decimals. You can tweak it to fit your needs.

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
    NSString *newString = [textField.text stringByReplacingCharactersInRange:range withString:string];
    NSString *expression = @"^[0-9]*((\\.|,)[0-9]{0,2})?$";
    NSError *error = nil;
    NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:expression options:NSRegularExpressionCaseInsensitive error:&error];
    NSUInteger numberOfMatches = [regex numberOfMatchesInString:newString options:0 range:NSMakeRange(0, [newString length])];
    return numberOfMatches != 0;
}
Rudolf Real
  • 1,709
  • 22
  • 26
nizx
  • 670
  • 6
  • 13
  • 2
    Nice answer. As a constructive critic, I'll recommend you to improve your RegEx knowledge. Things like "([0-9]+)?" can be replaced by "[0-9]*" It makes it more readable and also more performant. I already did the proper improvements to the answer. – Rudolf Real Mar 09 '15 at 14:36
  • @"^-?[0-9]*((\\.|,)[0-9]{0,2})?$" for ability to enter negative numbers too. – Raimundas Sakalauskas May 08 '17 at 17:39
8

Swift 3 Implement this UITextFieldDelegate method to prevent user from typing an invalid number:

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
    let text = (textField.text ?? "") as NSString
    let newText = text.replacingCharacters(in: range, with: string)
    if let regex = try? NSRegularExpression(pattern: "^[0-9]*((\\.|,)[0-9]*)?$", options: .caseInsensitive) {
        return regex.numberOfMatches(in: newText, options: .reportProgress, range: NSRange(location: 0, length: (newText as NSString).length)) > 0
    }
    return false
}

It is working with both comma or dot as decimal separator. You can also limit number of fraction digits using this pattern: "^[0-9]*((\\.|,)[0-9]{0,2})?$" (in this case 2).

Miroslav Hrivik
  • 702
  • 12
  • 12
6

For Swift 2.3 to prevent user for enter decimal number after two places -

func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool
{
    let decimalPlacesLimit = 2
    let rangeDot = txtPrice.text!.rangeOfString(".", options: .CaseInsensitiveSearch)

    if rangeDot?.count > 0
    {
        if (string == ".")
        {
            print("textField already contains a separator")
            return false
        }
        else {

            var explodedString = txtPrice.text!.componentsSeparatedByString(".")
            let decimalPart = explodedString[1]
            if decimalPart.characters.count >= decimalPlacesLimit && !(string == "")
            {
                print("textField already contains \(decimalPlacesLimit) decimal places")
                return false
            }
        }
    }
}
pawan gupta
  • 241
  • 4
  • 7
  • Welcome to Stack Overflow! While you may have solved this user's problem, code-only answers are not very helpful to users who come to this question in the future. Please edit your answer to explain why your code solves the original problem. – Joe C Feb 04 '17 at 08:12
6

Swift 4

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {

    // Allow to remove character (Backspace)
    if string == "" {
        return true
    }

   // Block multiple dot
    if (textField.text?.contains("."))! && string == "." {
        return false
    }

    // Check here decimal places
    if (textField.text?.contains("."))! {
        let limitDecimalPlace = 2
        let decimalPlace = textField.text?.components(separatedBy: ".").last
        if (decimalPlace?.count)! < limitDecimalPlace {
            return true
        }
        else {
            return false
        }
    }
    return true
}

Objective-C

//Create this variable in .h file or .m file
float _numberOfDecimal;

//assign value in viewDidLoad method
numberOfDecimal = 2;

#pragma mark - TextFieldDelegate
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {

    // Allow to remove character (Backspace)
    if ([string isEqualToString:@""]) {
        return true;
    }

    // Block multiple dot
    if ([textField.text containsString:@"."] && [string isEqualToString:@"."]) {
        return false;
    }

    // Check here decimal places
    if ([textField.text containsString:@"."]) {
        NSString *strDecimalPlace = [[textField.text componentsSeparatedByString:@"."] lastObject];

        if (strDecimalPlace.length < _numberOfDecimal) {
            return true;
        }
        else {
            return false;
        }
    }
    return true;
}
Vivek
  • 3,752
  • 26
  • 34
5

Building on the accepted answer, the following approach validates three cases that are helpful when dealing with money formats:

  1. Extremely large amounts
  2. More than 2 characters after the decimal point
  3. More than 1 decimal points

Make sure your text field's delegate is set properly, your class conforms to the UITextField protocol, and add the following delegate method.

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
  // Check for deletion of the $ sign
  if (range.location == 0 && [textField.text hasPrefix:@"$"])
    return NO;

  NSString *updatedText = [textField.text stringByReplacingCharactersInRange:range withString:string];
  NSArray *stringsArray = [updatedText componentsSeparatedByString:@"."];

  // Check for an absurdly large amount
  if (stringsArray.count > 0)
  {
    NSString *dollarAmount = stringsArray[0];
    if (dollarAmount.length > 6)
      return NO;
  }

  // Check for more than 2 chars after the decimal point
  if (stringsArray.count > 1)
  {
    NSString *centAmount = stringsArray[1];
    if (centAmount.length > 2)
      return NO;
  }

  // Check for a second decimal point
  if (stringsArray.count > 2)
    return NO;

  return YES;
}
Kyle Clegg
  • 36,212
  • 26
  • 127
  • 138
  • There is a case not correctly handled: if you have reached 6 characters in the dollarAmount, you may want to enter centAmount anyway. – Frédéric Adda Dec 15 '17 at 10:33
2
-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string 
{
    if(textField == min_textfield )
    {
        if([textField.text rangeOfString:@"."].location == NSNotFound)
        {
            if([string isEqualToString:@"."] )
            {
                flag_for_text = 1;
            }
            else 
            {
                textField.text = [NSMutableString stringWithFormat:@"%@",textField.text];
            }
        }
        else 
        {
            if([string isEqualToString:@"."])
            {
                return NO;
            }
            else 
            {
                textField.text = [NSMutableString stringWithFormat:@"%@",textField.text];
            }
        }
    }
}
jasveer
  • 21
  • 1
2

Try this :-

public func textView(textView: UITextView, shouldChangeTextInRange range: NSRange, replacementText text: String) -> Bool {

    if(text == "," || text == "." ){
        let countdots = textView.text!.componentsSeparatedByString(".").count - 1

        if countdots > 0 && (text == "." || text == "," )
        {
            return false
        }
    }

    return true
}
odemolliens
  • 2,431
  • 2
  • 28
  • 33
Abhijeet Mallick
  • 1,630
  • 2
  • 14
  • 19
1

Swift 3

No need to create an array and check count. Limit user can only place 1 decimal mark like this.

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
    if (textField.text?.contains("."))! && string.contains(".")
    {
        return false
    }
    else
    {
        return true
    }
}
RajeshKumar R
  • 13,989
  • 2
  • 35
  • 63
1

Swift 4

max number of Integers Numbers is 4 i.e., 9999, and max decimal digits limit is 2. So, max number can be 9999.99

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {


    // 100 is the tag value of our textfield
    /*or you may use "if textfield == myTextField{" if you have an IBOutlet to that textfield */
    if textField.tag == 100 {

        //max length limit of text is 8
        if textField.text!.count > 8 && string != "" {
            return false
        }

        let maxLength = 8
        let currentString: NSString = textField.text! as NSString 

// Use following code If you are inputting price to that text field and want $ to get inserted automatically at start when user starts typing in that textfield or you may put some other character at start instead of $. Otherwise comment the following 3 lines of if condition code

        if currentString.length == 0 {
            priceTextField.text = "$"
        }

//new string after inserting the new entered characters

        let newString: NSString =
            currentString.replacingCharacters(in: range, with: string) as NSString


        if newString.length > maxLength{
            return false
        }

        if (textField.text!.range(of: ".") != nil) {
            let numStr = newString.components(separatedBy: ".")
            if numStr.count>1{
                let decStr = numStr[1]
                if decStr.length > 2{
                    return false
                }
            }
        }

        var priceStr: String = newString as String

        if (textField.text!.range(of: "$") != nil) {
            priceStr = priceStr.replacingOccurrences(of: "$", with: "")
        }

        let price: Double = Double(priceStr) ?? 0

        if price > 9999.99{
            return false
        }

        switch string {
        case "0","1","2","3","4","5","6","7","8","9":
            return true
        case ".":
            let array = Array(textField.text!)
            var decimalCount = 0
            for character in array {
                if character == "." {
                    decimalCount = decimalCount + 1
                }
            }

            if decimalCount == 1 {
                return false
            } else {
                return true
            }
        default:

            let array = Array(string)
            if array.count == 0 {
                return true
            }
            return false
        }
    }
    return true
}
pravir
  • 280
  • 2
  • 12
0

In whatever object you set your UITextField's delegate to, add a method that answers to "[- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string]".

Then you can either use a NSNumberFormatter object or you can brute force check for an already existing decimal place mark (returning NO if a decimal mark already exists).

Community
  • 1
  • 1
Michael Dautermann
  • 86,557
  • 17
  • 155
  • 196
0

Short told, the number format is as follows [NSString stringWithFormat:@"%9.5f", x]; Where 5 is the decimal after ",".

0

I made the solution, that brings you control over decimal places count, so user can type only one decimal separator and you can also have a control over decimal places count.

Just set the decimalPlacesLimit value properly.

See the method:

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
    NSLog(@"text on the way: %@", string);
    NSUInteger decimalPlacesLimit = 2;

    NSRange rangeDot = [textField.text rangeOfString:@"." options:NSCaseInsensitiveSearch];
    NSRange rangeComma = [textField.text rangeOfString:@"," options:NSCaseInsensitiveSearch];
    if (rangeDot.length > 0 || rangeComma.length > 0){
        if([string isEqualToString:@"."]) {
            NSLog(@"textField already contains a separator");
            return NO;
        } else {
            NSArray *explodedString = [textField.text componentsSeparatedByString:@"."];
            NSString *decimalPart = explodedString[1];
            if (decimalPart.length >= decimalPlacesLimit && ![string isEqualToString:@""]) {
                NSLog(@"textField already contains %d decimal places", decimalPlacesLimit);
                return NO;
            }
        }
    }

    return YES;
}
pedrouan
  • 11,915
  • 2
  • 55
  • 71
0

Swift 4

The efficient and easy way to avoid multiple decimal points (. or ,) in UITextField:

    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
    if(string == "," || string == "." ){

        if ((textField.text?.contains(","))! || (textField.text?.contains("."))!){
            return false
        }
    }
    return true
}
0
-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
    if([string isEqualToString:@"."]) {
        BOOL containsDecimal = [textField.text containsString:@"."];
        return !containsDecimal;
    }
    return YES;
}

If textfield text already contains a '.' then return NO else return YES.

0

SWIFT 5

Improvement

Info : do not allow :

  • separator at the beginning
  • zero plus another digit at the start except when you add a separator after

1: set the keyboard type to : Decimal Pad

enter image description here

2: copy past

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
    //!\ set the keyboard type to : Decimal Pad /!\\
    // CUSTOM SETUP
    let c = NSLocale.current.decimalSeparator ?? "."
    let limitBeforeSeparator = 2
    let limitAfterSeparator = 2
    // ---------
    
    
    var validatorUserInput:Bool = false
    
    let text = (textField.text ?? "") as NSString
    let newText = text.replacingCharacters(in: range, with: string)
    
    
    // Validator
    let pattern = "(?!0[0-9])\\d*(?!\\\(c))^[0-9]{0,\(limitBeforeSeparator)}((\\\(c))[0-9]{0,\(limitAfterSeparator)})?$"
    if let regex = try? NSRegularExpression(pattern: pattern, options: .caseInsensitive) {
        validatorUserInput = regex.numberOfMatches(in: newText, options: .reportProgress, range: NSRange(location: 0, length: (newText as NSString).length)) > 0
    }
     
    
    if validatorUserInput {
        // setting data or something eles before the return
        if let char = string.cString(using: String.Encoding.utf8) {
            let isBackSpace = strcmp(char, "\\b")
            if (isBackSpace == -92 && textField.text?.count == 1) {
                print("Backspace was pressed")
                print(newText)
                // do something...
                
            } else {
                print("Number Added")
                print(newText)
                // do something...
                
            }
        }
        return validatorUserInput
    } else {
        return validatorUserInput
    }
}

3: set in the method, if you want x maximum number of digits before and after the separator

 let limitBeforeSeparator = 2 
 let limitAfterSeparator = 2
Murphy
  • 469
  • 4
  • 6