2

I need to implement a validation in TextField that will stop the user to enter anything besides the allowed requirements.

The validations must contain:

  • Allow only numbers and decimal points .
  • The highest allowed number should be 9999999999
  • Stop users from writing two . in the TextField
  • Allow a maximum of two decimal numbers after the point (1, 1.9, and 1.99 are acceptable, 1.999 is not acceptable).
  • It should be aware that if the user tries to paste some text or any number that doesn't pass the requirements, it should not allow that.

I tried an implementation, but sometimes is not working. I don't understand how is that affected.

var dotLocation = Int()

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
    
    if string == "," {
        textField.text = textField.text! + "."
        return false
    }
    
    let nonNumberSet = CharacterSet(charactersIn: "0123456789.").inverted
    let allowedCharacters = CharacterSet(charactersIn:"0123456789.")//Here change this characters based on your requirement
    let characterSet = CharacterSet(charactersIn: string)
    
    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 allowedCharacters.isSuperset(of: characterSet)
        
    } else {
        return allowedCharacters.isSuperset(of: characterSet)
    }
    
    return true
}

What would be the best implementation in such a case? Thanks for your contribution.

2 Answers2

0

There may be multiple ways of achieving the expected result. This is the first thing came to my mind.

    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        
        //hamdle the maximum and minimum decimal counts
        let formatter = NumberFormatter()
        formatter.numberStyle = .none
        formatter.maximumFractionDigits = 2
        formatter.minimumFractionDigits = 0
        
        textField.textColor = UIColor(red: -238.0/255, green: -87.0/255, blue: -61.0/255, alpha: 1.0)
        
        let finalString = "\(textField.text ?? "")\(string)"
        let dpRemovedString = finalString.filter{$0 != "."}
        let dpCount = finalString.filter{$0 == "."}.count
        
        //check for the decimal poin count
        if dpCount > 1{
            return false
        }
        
        //check there is only numbers
        if CharacterSet.decimalDigits.isSuperset(of: CharacterSet(charactersIn: dpRemovedString)){
            if dpRemovedString == ""{
                return false
            }
            var formattedNumber = formatter.string(for: Double(finalString))
            //here highest alllowed number is 999
            if Float(formattedNumber!)! >= 999{
                return false
            }else{
                if string == "."{
                    formattedNumber = formattedNumber! + string
                }
                textField.text = formattedNumber
            }
            return false
        }else{
            return false
        }
    
    }

Check the answers in here to stop user from pasting things in the textfield.

udi
  • 3,672
  • 2
  • 12
  • 33
0

You can use Regex to shorten your validation logic.

Make your number checking here:

    fileprivate func isValidNumber(numStr: String) -> Bool {
        var returnValue = true
        let numberRegex = "^[0-9]*(.?)[0-9]{0,2}$"
        do {
            let regex = try NSRegularExpression(pattern: numberRegex)
            let nsString = numStr as NSString
            let results = regex.matches(in: numStr, range: NSRange(location: 0, length: nsString.length))
            if results.isEmpty {
                returnValue = false
            }
        } catch let error as NSError {
            print("invalid regex: \(error.localizedDescription)")
            returnValue = false
        }
        return  returnValue
    }

Then use it inside your func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool:

    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        let currentString = (textField.text ?? "") as NSString
        
        // get final text
        let newString = currentString.replacingCharacters(in: range, with: string)
        
        // allow backspace
        if string.isEmpty && range.length > 0 { return true }
        
        if self.isValidNumber(numStr: newString) {
            if let currentDecimalVal = Decimal.init(string: currentString as String) {
                // If current is already equal max value, skip change
                if currentDecimalVal.isEqual(to: self.maxNumber) { return false }
            }
            if let newDecimalVal = Decimal.init(string: newString) {
                // If new is still less than max, allow change
                return newDecimalVal.isLess(than: self.maxNumber)
            }
        }
        return false
    }

You can test your regex here: https://www.regextester.com/.

You may need to add \ before characters that need to be escaped, in Regex string.

Neklas
  • 504
  • 1
  • 9