将用户输入限制为Swift中的有效十进制数

时间:2014-10-25 20:19:09

标签: ios objective-c ipad swift

我已经找到了很多关于如何在objective-c中执行此操作的指南,但我希望看到更多以Swift为导向的方法。

我有一个UITextField,用户输入货币价格。文本字段调用十进制键盘。但是,在iPad上,出现的键盘有一系列非小数符号。

基本上,对于每一次按键,我都希望不能将非数字或超过一个小数的任何内容输入到字段中。如果输入小数,我想不可能输入第二个小数。如果删除小数,我想确保用户可以再次输入小数。

关于如何在swift中正确执行此操作的任何想法?

我也看到像这里发布的解决方案: Limit UITextField to one decimal point Swift 但我不知道在哪里放置功能或我应该如何调用它们。每当我尝试在参数中输入NSRange时,我都会收到错误消息,表示我没有正确创建范围。

19 个答案:

答案 0 :(得分:12)

这是一个简单的例子:

import UIKit

class ViewController: UIViewController, UITextFieldDelegate {

    @IBOutlet weak var textField: UITextField!

    override func viewDidLoad() {
        super.viewDidLoad()

        self.textField.delegate = self

    }

    //Textfield delegates
    func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool { // return NO to not change text

        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++
                }
            }

            if decimalCount == 1 {
                return false
            } else {
                return true
            }
        default:
            let array = Array(string)
            if array.count == 0 {
                return true
            }
            return false
        }
    }
}

答案 1 :(得分:7)

通过使用NSScanner测试新字符串是否为数字,需要考虑多个小数:

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

    // Get the attempted new string by replacing the new characters in the
    // appropriate range
    let newString = (textField.text as NSString).stringByReplacingCharactersInRange(range, withString: string)

    if newString.length > 0 {

        // Find out whether the new string is numeric by using an NSScanner.
        // The scanDecimal method is invoked with NULL as value to simply scan
        // past a decimal integer representation.
        let scanner: NSScanner = NSScanner(string:newString)
        let isNumeric = scanner.scanDecimal(nil) && scanner.atEnd

        return isNumeric

    } else {

        // To allow for an empty text field
        return true
    }

}

答案 2 :(得分:7)

Swift 2 版@Steve Rosenberg的解决方案

如果您不需要将输入限制为最多2个小数位(即“12.34”OK,“12.345”不正常),则在开头删除4行。

import UIKit

class ViewController: UIViewController, UITextFieldDelegate {

    @IBOutlet weak var textField: UITextField!

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

    //Textfield delegates
    func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool { // return false to not change text
        // max 2 fractional digits allowed
        let newText = (textField.text! as NSString).stringByReplacingCharactersInRange(range, withString: string)
        let regex = try! NSRegularExpression(pattern: "\\..{3,}", options: [])
        let matches = regex.matchesInString(newText, options:[], range:NSMakeRange(0, newText.characters.count))
        guard matches.count == 0 else { return false }

        switch string {
        case "0","1","2","3","4","5","6","7","8","9":
            return true
        case ".":
            let array = textField.text?.characters.map { String($0) }
            var decimalCount = 0
            for character in array! {
                if character == "." {
                    decimalCount++
                }
            }
            if decimalCount == 1 {
                return false
            } else {
                return true
            }
        default:
            let array = string.characters.map { String($0) }
            if array.count == 0 {
                return true
            }
            return false
        }
    }
}

答案 3 :(得分:5)

所有答案都使用'。'作为小数的有效分隔符,但在不同的本地化中,它可能是错误的。

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
    guard !string.isEmpty else {
        return true
    }

    let currentText = textField.text ?? ""
    let replacementText = (currentText as NSString).replacingCharacters(in: range, with: string)

    return replacementText.isDecimal()
}


extension String{
   func isDecimal()->Bool{
       let formatter = NumberFormatter()
       formatter.allowsFloats = true
       formatter.locale = Locale.current
       return formatter.number(from: self) != nil
   }
}

答案 4 :(得分:3)

这是受到了Wye的答案的启发,但它更紧​​凑,并且在我需要数字/十进制字段的地方工作。您可以通过修改正则表达式来适应只接受整数(取出spark-submit并留下.?\\d{0,2})。同样,如果您不想限制小数点后的位数,则可以删除该限制(只需将其更改为^\\d*$

^\\d*\\.?\\d*

这允许构造数字字符串而不会拒绝输入,例如,以下是所有有效输入, func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool { let newString = (_timeQuantityField.text! as NSString).stringByReplacingCharactersInRange(range, withString: string) let decimalRegex = try! NSRegularExpression(pattern: "^\\d*\\.?\\d{0,2}$", options: []) let matches = decimalRegex.matchesInString(newString, options: [], range: NSMakeRange(0, newString.characters.count)) if matches.count == 1 { return true } return false } 给出有效结果):

  • (newString as NSString).floatValue(即空字符串)产生0.0
  • 收益率为0.0
  • .收益率为1.0
  • 1.收益率为0.1

答案 5 :(得分:2)

以下是最简单的方法:

func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
    if (textField.text?.componentsSeparatedByString(".").count > 1 && string == ".")
    {
        return false
    }
    return string == "" || (string == "." || Float(string) != nil)
}

答案 6 :(得分:2)

经过测试并在 Swift 3和Swift 4 中工作,您也可以进行以下检查

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

        let existingTextHasDecimalSeparator = textField.text?.rangeOfString(".")
        let replacementTextHasDecimalSeparator = string.rangeOfString(".")

        if existingTextHasDecimalSeparator != nil && replacementTextHasDecimalSeparator != nil {
            return false
        }
        else {
            return true
        }
    }

答案 7 :(得分:2)

这是Swift 4解决方案:

import struct Foundation.CharacterSet

extension String {
    var onlyNumbers: String {
        let charset = CharacterSet.punctuationCharacters.union(CharacterSet.decimalDigits).inverted

        return components(separatedBy: charset).joined()
    }
}

答案 8 :(得分:2)

Swift 3 实现此UITextFieldDelegate方法以防止用户输入无效的数字:

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]{0,2})?$", options: .caseInsensitive) {
        return regex.numberOfMatches(in: newText, options: .reportProgress, range: NSRange(location: 0, length: (newText as NSString).length)) > 0
    }
    return false
}

它使用逗号或点作为小数分隔符,并允许2个小数位。

答案 9 :(得分:1)

这是我使用的。如果返回false,则调用者将使用textField.deleteBackward()删除最后一个(违规)字符。

func isValidNumber(text: String) -> Bool {
    let validChars: Set<Character> = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "."]
    return (Set(text).isSubset(of: validChars) && ((text.components(separatedBy: ".").count - 1) <= 1))
}

或者您可以在函数中完成所有操作:

func isValidNumber2(textField: UITextField) -> Bool {
    let validChars: Set<Character> = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "."]
    let validNum = Set(textField.text!).isSubset(of: validChars) && ((textField.text!.components(separatedBy: ".").count - 1) <= 1)

    if !validNum {
        textField.deleteBackward()
    }
    return (validNum)
}

两者都简短,清晰,简单,高效。 (似乎第二个更干净......意见?)但是他们没有将输入限制在一个小数点......

答案 10 :(得分:1)

改善Naishta在 Swift 4 中的回应,这里有一个片段,允许您将文本字段长度限制为10个字符(额外奖励 - 不是帖子创建者要求的)和单个小数点

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

    // Max 10 characters.
    let newLength = text.count + string.count - range.length
    if newLength > 10 { return false }

    // Max one decimal point.
    let existingTextHasDecimalSeparator = text.range(of: ".")
    let replacementTextHasDecimalSeparator = string.range(of: ".")
    if existingTextHasDecimalSeparator != nil  && replacementTextHasDecimalSeparator != nil  { return false }

    return true
  }

答案 11 :(得分:1)

以同样的方式做。下面的代码并没有防范多个.,但也可以做你想要的。像你一样延伸它。

class Foo: NSObject, UITextFieldDelegate {

    func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
        var result = true
        if countElements(string) > 0 {
            let numericInput = NSCharacterSet(charactersInString: "0123456789.-").invertedSet
            if let badRange = string.rangeOfCharacterFromSet(numericInput) {
                let substring = string.substringToIndex(badRange.startIndex)
                let oldString: NSString = textField.text // necessary so we can use the NSRange object passed in.
                textField.text = oldString.stringByReplacingCharactersInRange(range, withString: substring)
                result = false
            }
        }
        return result
    }
}

答案 12 :(得分:0)

我们可以做得更好,而无需对允许的字符和分隔符进行硬编码。尤其是分隔符,因为它在不同的区域可能有所不同。我们还需要注意,用户可能会移动光标并粘贴文本。这是一个考虑到这一点的验证功能:

static func validateDecimalNumberText(for textField: UITextField, replacementStringRange: NSRange, string: String) -> Bool {

    // Back key
    if string.isEmpty {
        return true
    }

    // Allowed charachters include decimal digits and the separator determined by number foramtter's (current) locale
    let numberFormatter = NumberFormatter()
    numberFormatter.maximumFractionDigits = 2
    let allowedCharacters = CharacterSet.decimalDigits.union(CharacterSet(charactersIn: numberFormatter.decimalSeparator))
    let characterSet = CharacterSet(charactersIn: string)

    // False if string contains not allowed characters
    if !allowedCharacters.isSuperset(of: characterSet) {
        return false
    }

    // Check for decimal separator
    if let input = textField.text {
        if let range = input.range(of: numberFormatter.decimalSeparator) {
            let endIndex = input.index(input.startIndex, offsetBy: input.distance(from: input.startIndex, to: range.upperBound))
            let decimals = input.substring(from: endIndex)

            // If the replacement string contains a decimal seperator and there is already one, return false
            if input.contains(numberFormatter.decimalSeparator) && string == numberFormatter.decimalSeparator {
                return false
            }

            // If a replacement string is before the separator then true
            if replacementStringRange.location < endIndex.encodedOffset {
                return true
            } else {
                // If the string will exceed the max number of fraction digits, then return false, else true
                return string.count + decimals.count <= numberFormatter.maximumFractionDigits
            }
        }
    }

    return true
}

还有textfield委托方法:

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
    return Utils.validateDecimalNumberText(for: textField, replacementStringRange: range, string: string)
}

答案 13 :(得分:0)

func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
        if (range.location == 0 && string == ".") {
            return false
        }
        else if string == "."{
            if textField.text?.componentsSeparatedByString(".").count > 1{
                return false
            }
        }
        let aSet = NSCharacterSet(charactersInString:"0123456789.").invertedSet
        let compSepByCharInSet = string.componentsSeparatedByCharactersInSet(aSet)
        let numberFiltered = compSepByCharInSet.joinWithSeparator("")
        return string == numberFiltered
}

答案 14 :(得分:0)

雨燕4 使用@SteveRosenberg的答案并根据我的要求写了这个

最大整数个数为4,即9999,最大十进制数限制为2。因此,最大个数可以为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
}

答案 15 :(得分:0)

Swift 4.2

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
    let numberCharSet = CharacterSet(charactersIn: ".").union(CharacterSet.decimalDigits)
    let characterSet = CharacterSet(charactersIn: string)
    return numberCharSet.isSuperset(of: characterSet)
}

这允许从0 to 9起的数字和小数点.

答案 16 :(得分:0)

  • 数字
  • 2 小数位。
  • 空格。
  • 小数点是逗号

如果需要指定小数点,请更改[.,]

let regex = try! NSRegularExpression(pattern: "^[0-9]*([.,][0-9]{0,2})?$", options: .caseInsensitive)

if let newText = (textFieldView.textField.text as NSString?)?.replacingCharacters(in: range, with: string) {
    return regex.firstMatch(in: newText, options: [], range: NSRange(location: 0, length: newText.count)) != nil

} else {
    return false
}

答案 17 :(得分:0)

现在我正在使用不带正则表达式的解决方案。希望对您有帮助:D

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
    guard let currentText = (textField.text as NSString?)?.replacingCharacters(in: range, with: string) else { return true }

    if textField == txtFieldWeight || textField == txtFieldHeight {
        let newText = currentText.replacingOccurrences(of: ",", with: ".")
        let isDecimal = Float(newText) != nil
        return isDecimal
    } 

    return true
}

答案 18 :(得分:-1)

SWIFT 3.2和4.0 Chis会将用户限制在十进制后的两位数,并限制它们添加一个小数点。 确保将键盘类型设置为十进制。

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

        // if keyboard type is decimal then apply just one dot
       if(textField.keyboardType == .decimalPad)
       {


        // geting counts of dot
        let countdots = (textField.text?.components(separatedBy:".").count)! - 1

        // if there is more then one dot then
        if(countdots > 0)
        {

            // creating array by dot
             var digitArray = textField.text?.components(separatedBy:".")


            let decimalDigits = digitArray![1]

            // limiting only 2 digits after decimal point
            if(decimalDigits.count > 1 )
            {
                return false;
            }

        }
        // limiting to only 1  decimal point
            if countdots > 0 && string == "."
            {

                return false
            }


        }
        return true
    }