PennyWise
PennyWise

Reputation: 647

How to limit decimal input value in UITextField

I am currently checking my UITextField, which is set to show Numeric Keypad in shouldChangeCharactersIn to limit the input to only one decimal separator and only 2 decimal points like this (thanks to this question):

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

    let decimalSeparator = String(Locale.current.decimalSeparator ?? ".")

    if (textField.text?.contains(decimalSeparator))! {

        let limitDecimalPlace = 2
        let decimalPlace = textField.text?.components(separatedBy: decimalSeparator).last

        if (decimalPlace?.count)! < limitDecimalPlace {

            return true

        } else {

           return false

        }

    }

}

This works great. However, it is now possible to insert whatever value the user wants, which I want to limit to a value lower than 999. I used to check the length to allow only 3 characters, but now I want to allow following values (for example):

143
542.25
283.02
19.22
847.25

But I don't want to allow:

2222
3841.11
999.99

How could I do that?

Upvotes: 2

Views: 1096

Answers (1)

Rob
Rob

Reputation: 437432

You probably need two checks:

  1. Make sure it in the form of xxx.xx. This sort of pattern matching is often achieved by using regular expression search.

    The trick here is to make sure you support all permutations with and without decimal place, where the fractional digits is two or fewer digits and the integer digits is three or fewer digits.

  2. Try converting it to a number and check that the value is less than 999.

Thus:

let formatter = NumberFormatter()

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
    let candidate = ((textField.text ?? "") as NSString).replacingCharacters(in: range, with: string)
    let separator = formatter.decimalSeparator!

    if candidate == "" { return true }

    let isWellFormatted = candidate.range(of: "^[0-9]{1,3}([\(separator)][0-9]{0,2})?$", options: .regularExpression) != nil

    if isWellFormatted,
        let value = formatter.number(from: candidate)?.doubleValue,
        value >= 0,
        value < 999 {
            return true
    }

    return false
}

Note:

  • I’m assuming you want users to be able to honor their device’s localization settings (e.g. let a German user enter 123,45 because they use , as the decimal separator).

  • The regular expression, "^[0-9]{1,3}([\(separator)][0-9]{0,2})?$” probably looks a little hairy if you’re not used to regex.

    • The ^ matches the start of the string;
    • The [0-9] obviously matches any digit;
    • The {1,3} matches between one and three integer digits;
    • The (...)? says “optionally, look for the following”;
    • Again, [0-9]{0,2} means “between zero and two fractional digits; and
    • The $ matches the end of the string.

Upvotes: 3

Related Questions