Swift: Limiting the user to ONE DECIMAL POINT in UITextView

1.3k views Asked by At

Basically I would like to limit the user to enter only ONE DECIMAL POINT in a UITextView object, I would also like to limit the number after the ONE DECIMAL POINT to TWO (i.e two decimal places numbers). I am using the below code which currently works in preventing the user from enter more than ONE DECIMAL POINT:

// The below extension basically counts the number of a specific character in a string:

extension String {

func countInstances(of stringToFind: String) -> Int {

    var stringToSearch = self

    var count = 0

    while let foundRange = stringToSearch.range(of: stringToFind, options: .diacriticInsensitive) {

        stringToSearch = stringToSearch.replacingCharacters(in: foundRange, with: "")

        count += 1

    }

    return count

}

}

class ViewController: UIViewController, UITextViewDelegate, UIPopoverPresentationControllerDelegate {
override func viewDidLoad() {

    super.viewDidLoad()

    self.indicativeDesignWorkingLifeTextView.delegate = self

    indicativeDesignWorkingLifeTextView.keyboardType = .decimalPad

}

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

    let decimalCount = indicativeDesignWorkingLifeTextView.text.countInstances(of: ".")


    if decimalCount > 0 {

        return false

    } else {

        return true

    }

}

}

However, the problem with the above code is that it works in terms of limiting the user to enter only ONE DECIMAL POINT inside the UITextView but it also limit the user from doing anything after entering the decimal point. It does not even allow the user to delete the one decimal point that he entered and insert a new one or any other number, Basically as soon as one decimal point is entered the user cannot edit anything inside the UITextView. Any idea on how can I amend this? and also limit the user to enter only number to two decimal places?

3

There are 3 answers

4
mango On

Depending on if your use case permits it, I'd strongly urge you to use a supported iOS provided keyboardType so that you don't have to do all the work yourself.

    indicativeDesignWorkingLifeTextView.keyboardType = .decimalPad
5
algrid On

Your UITextViewDelegate method name is wrong. It has to be

func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, 
   replacementText text: String) -> Bool
3
Alain T. On

You could evaluate the resulting text and check if there is more than one decimal point in it.

for example:

   if let newText = (indicativeDesignWorkingLifeTextView.text as NSString!)?
                    .replacingCharacters(in: range, with: text),
      newText.components(separatedBy:".").count > 2
   { return false }

For some reason, Apple decided to pass an old NSRange type for the substring rather than the Swift type (Range < String.Index >) requiring an unwieldy type cast to NSString!. The condition would have been much cleaner and legible otherwise.

If you want the whole condition to allow only decimal characters, with no more than one decimal point and max 2 digits after the decimal, you can combine it like this:

   if let newText = (textView.text as NSString!)?.replacingCharacters(in: range, with: text),
      case let parts = newText.components(separatedBy:"."),
      parts.count > 2 
      || text.characters.contains(where:{ !"01234567890.".characters.contains($0) })
      || parts.count == 2 && parts[1].characters.count > 2
   { return false }