KawaiKx
KawaiKx

Reputation: 9918

How to limit the textfield entry to 2 decimal places in swift 4?

I have a textfield and I want to limit the entry to max 2 decimal places.

number like 12.34 is allowed but not 12.345

How do I do it?

Upvotes: 22

Views: 20896

Answers (8)

Enrique
Enrique

Reputation: 1623

Following the Code Different's answer, I've improved the code to support a different Locale and different UITextFields in the same class.

  func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
    guard textField.keyboardType == .decimalPad, let oldText = textField.text, let r = Range(range, in: oldText) else {
        return true
    }

    let newText = oldText.replacingCharacters(in: r, with: string)
    let isNumeric = newText.isEmpty || (Double(newText) != nil)

    let formatter = NumberFormatter()
    formatter.locale = Locale.current
    let decimalPoint = formatter.decimalSeparator ?? "."
    let numberOfDots = newText.components(separatedBy: decimalPoint).count - 1

    let numberOfDecimalDigits: Int
    if let dotIndex = newText.index(of: ".") {
        numberOfDecimalDigits = newText.distance(from: dotIndex, to: newText.endIndex) - 1
    } else {
        numberOfDecimalDigits = 0
    }
    return isNumeric && numberOfDots <= 1 && numberOfDecimalDigits <= 2
  }

Upvotes: 2

Golompse
Golompse

Reputation: 101

Swift 5. Adding to Enrique's answer (based on Code Different baseline), I found the need to allow the Delete key to operate anywhere on the line along with a sign (+ or -) or decimal point at the beginning of the input line.

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
    // Limit number fields to +/- Real #s: sign as first character, one decimal separator, one decimal place after separator
    if string.isEmpty { return true }   // Allow delete key anywhere!
    guard let oldText = textField.text, let rng = Range(range, in: oldText) else {
        return true
    }
    let newText = oldText.replacingCharacters(in: rng, with: string)

    let isNumeric = newText.isEmpty || (Double(newText) != nil)

    let formatter = NumberFormatter()
    formatter.locale = .current
    let decimalPoint = formatter.decimalSeparator ?? "."
    let numberOfDots = newText.components(separatedBy: decimalPoint).count - 1

    let numberOfDecimalDigits: Int
    if let dotIndex = newText.firstIndex(of: ".") {
        numberOfDecimalDigits = newText.distance(from: dotIndex, to: newText.endIndex) - 1
    } else {
        numberOfDecimalDigits = 0
    }
    if newText.count == 1 && !isNumeric {   // Allow first character to be a sign or decimal point
        return CharacterSet(charactersIn: "+-" + decimalPoint).isSuperset(of: CharacterSet(charactersIn: string))
    }
    return isNumeric && numberOfDots <= 1 && numberOfDecimalDigits <= 1
}

Upvotes: 0

Khurshid
Khurshid

Reputation: 117

Only Two decimal point allow and only one "." allow

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

                    let text: NSString = textField.text! as NSString
                    let resultString = text.replacingCharacters(in: range, with: string)


        //Check the specific textField 
         if textField == costTextField {
             let textArray = resultString.components(separatedBy: ".")
             if textArray.count > 2 { //Allow only one "."
                return false
             }
           if textArray.count == 2 {
              let lastString = textArray.last
             if lastString!.count > 2 { //Check number of decimal places 
                   return false
               }
            }
          }
           return true
       }

Upvotes: 5

Simon Mo
Simon Mo

Reputation: 773

None of the answers handled the decimalSeparator and all the edge cases I came across so I decided to write my own.

extension YourController: UITextFieldDelegate {
    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        guard let text = textField.text, let decimalSeparator = NSLocale.current.decimalSeparator else {
            return true
        }

         var splitText = text.components(separatedBy: decimalSeparator)
         let totalDecimalSeparators = splitText.count - 1
         let isEditingEnd = (text.count - 3) < range.lowerBound

         splitText.removeFirst()

         // Check if we will exceed 2 dp
         if
             splitText.last?.count ?? 0 > 1 && string.count != 0 &&
             isEditingEnd
         {
             return false
         }

         // If there is already a dot we don't want to allow further dots
         if totalDecimalSeparators > 0 && string == decimalSeparator {
             return false
         }

         // Only allow numbers and decimal separator
         switch(string) {
         case "", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", decimalSeparator:
             return true
         default:
             return false
         }
     }
 }

Upvotes: 2

Solairaj
Solairaj

Reputation: 119

In Swift 4.

TextField have 10 digit limit and after decimal 2 digit limit (you can change the Limits). The dot will allow only one time in the textField.

class ViewController: UIViewController,UITextFieldDelegate {

    var dotLocation = Int()

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


        let nonNumberSet = CharacterSet(charactersIn: "0123456789.").inverted

        if Int(range.length) == 0 && string.count == 0 {
            return true
        }

        if (string == ".") {
            if Int(range.location) == 0 {
                return false
            }
            if dotLocation == 0 {
                dotLocation = range.location
                return true
            } else {
                return false
            }
        }

        if range.location == dotLocation && string.count == 0 {
            dotLocation = 0
        }

        if dotLocation > 0 && range.location > dotLocation + 2 {
            return false
        }

        if range.location >= 10 {

            if dotLocation >= 10 || string.count == 0 {
                return true
            } else if range.location > dotLocation + 2 {
                return false
            }

            var newValue = (textField.text as NSString?)?.replacingCharacters(in: range, with: string)
            newValue = newValue?.components(separatedBy: nonNumberSet).joined(separator: "")
            textField.text = newValue

            return false

        } else {
            return true
        }

    }

}

Upvotes: 6

Akbar Khan
Akbar Khan

Reputation: 2417

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

            if let text = textField.text {
                let isDeleteKey = string.isEmpty

                if !isDeleteKey {
                    if text.contains(dotString) {
                        if text.components(separatedBy: dotString)[1].count == 2 {

                                    return false

                        }

                    }

                }
            }
            return true
         }

Upvotes: 1

Mihail Salari
Mihail Salari

Reputation: 1571

Guys, here's the solution:

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

        if let text = textField.text {
            let isDeleteKey = string.isEmpty

            if !isDeleteKey {
                if text.contains(dotString) {
                    if text.components(separatedBy: dotString)[1].count == 2 {

                                return false

                    }

                }

            }
        }
  }

Upvotes: 5

Code Different
Code Different

Reputation: 93161

Set your controller as the delegate for the text field and check if the proposed string satisfy your requirements:

override func viewDidLoad() {
    super.viewDidLoad()
    textField.delegate = self
    textField.keyboardType = .decimalPad
}

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
    guard let oldText = textField.text, let r = Range(range, in: oldText) else {
        return true
    }

    let newText = oldText.replacingCharacters(in: r, with: string)
    let isNumeric = newText.isEmpty || (Double(newText) != nil)
    let numberOfDots = newText.components(separatedBy: ".").count - 1

    let numberOfDecimalDigits: Int
    if let dotIndex = newText.index(of: ".") {
        numberOfDecimalDigits = newText.distance(from: dotIndex, to: newText.endIndex) - 1
    } else {
        numberOfDecimalDigits = 0
    }

    return isNumeric && numberOfDots <= 1 && numberOfDecimalDigits <= 2
}

Upvotes: 53

Related Questions