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

Posted

技术标签:

【中文标题】在 Swift 中将用户输入限制为有效的十进制数【英文标题】:Limiting user input to a valid decimal number in Swift 【发布时间】:2014-10-25 20:19:08 【问题描述】:

我找到了很多关于如何在 Objective-c 中做到这一点的指南,但我希望看到一种更面向 Swift 的方式来做到这一点。

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

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

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

我还看到了类似此处发布的解决方案: Limit UITextField to one decimal point Swift 但我不知道在哪里放置函数或我应该如何调用它们。每当我尝试在参数中输入 NSRange 时,都会收到一个错误,提示我没有正确创建范围。

【问题讨论】:

你能写出你正在使用的代码吗? 【参考方案1】:

这是一个简单的例子:

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,以确保输入的数字始终是有效的双精度数。 否 - 在 vi​​ewDidLoad 之外。它自称!使用我给你的整个课程。 确定我们可以添加删除键或退格键 - 给我一两分钟 更新了默认情况。这现在应该允许退格/删除键。 这不是一个好的解决方案,因为它只适用于非常有限的语言环境。并非所有用户都将句点用作小数分隔符,也并非所有用户都将字符 0-9 用于数字。如果用户将文本粘贴到文本字段中,此代码也将不起作用。【参考方案2】:

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

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
   

【讨论】:

不工作,无法添加“。”或“,”在俄罗斯当地语言中【参考方案3】:

通过使用 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
    


【讨论】:

【参考方案4】:

Swift 2 版本的@Steve Rosenberg 解决方案

如果您不需要将输入限制为最多 2 个小数位(即“12.34”可以,“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
        
    

【讨论】:

我应该如何限制“。”的前一个。在这个相同的文本字段文本中,值长度为 9。?【参考方案5】:

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 个小数位。

【讨论】:

【参考方案6】:

斯威夫特 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 和小数点. 的数字

【讨论】:

它还允许多个小数点(例如 123.123.123)因此使其无效的十进制数。【参考方案7】:

这受到 wye 的回答的启发,但更紧凑一些,并且在我想要一个数字/十进制字段的地方对我有用。您可以通过修改正则表达式来适应只接受整数(取出.?\\d0,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*\\.?\\d0,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. 产生 1.0 .1 产生 0.1

【讨论】:

【参考方案8】:

这是最简单的方法:

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)

【讨论】:

一般来说,如果答案包含对代码的用途的解释,以及为什么在不介绍其他人的情况下解决问题的原因,答案会更有帮助。【参考方案9】:

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
        
    

【讨论】:

【参考方案10】:

改进了 Naishta 在 Swift 4 中的响应,这是一个允许您将文本字段长度限制为 10 个字符的 sn-p(额外奖励 - 帖子创建者未要求 ) 和一个一个小数点

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】:

这是一个 Swift 4 解决方案:

import struct Foundation.CharacterSet

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

        return components(separatedBy: charset).joined()
    

【讨论】:

【参考方案12】:

以同样的方式进行。下面的代码不能防止多个.,而是做你想要的。随意扩展。

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
    

【讨论】:

【参考方案13】:

这是我使用的。如果返回 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)

两者都简短、清晰、简单且高效。 (似乎第二个更干净......意见?)但他们不限制输入到一个小数点......

【讨论】:

【参考方案14】:

斯威夫特 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】:
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

【讨论】:

【参考方案16】:

如果不对允许的字符和分隔符进行硬编码,我们可以做得更好。尤其是分隔符,因为它在不同的语言环境中可能不同。我们还需要注意,用户可能会移动光标并粘贴文本。这是一个考虑到这一点的验证函数:

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

以及文本字段委托方法:

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

【讨论】:

【参考方案17】: 只有数字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

【讨论】:

【参考方案18】:

现在我正在使用这个没有正则表达式的解决方案。希望对你有帮助: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

【讨论】:

【参考方案19】:

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
    

【讨论】:

以上是关于在 Swift 中将用户输入限制为有效的十进制数的主要内容,如果未能解决你的问题,请参考以下文章

在 Swift 中将文本限制为一定数量的单词

如何在 Swift 中将十六进制字符串转换为 CString?

如何在 Swift 中将 Int 转换为十六进制字符串

如何在 SQL 中将值显示为 Y 或 N?

如何在 Swift 中将十六进制转换为有符号浮点数?适用于正数但不适用于负数

如何在 Delphi 中将浮点数转换为字符串,指定有效数字,而不是十进制数字(而不是 G 格式)