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

12

我发现了许多使用Objective-C实现此功能的指南,但我想看到更多基于Swift的实现方法。

我有一个UITextField用于用户输入货币价格。文本框调用十进制键盘。然而,在iPad上,弹出的键盘有整个一系列的非十进制符号。

基本上,对于每次按键,我都想让非数字或超过一个小数点以外的任何内容无法输入到该字段中。如果已输入小数点,则不允许再输入第二个小数点。如果删除小数点,则需要确保用户可以重新输入小数点。

有什么好的Swift实现方式吗?

我还看到像这里发布的解决方案: Limit UITextField to one decimal point Swift 但我不知道应将函数放在哪里,也不知道如何调用它们。每当我尝试在参数中放入NSRange时,就会收到错误提示说我没有正确创建范围。


1
请问你可以把你正在使用的代码写出来吗? - ielyamani
19个回答

12

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

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

无法工作,无法在俄语本地语言中添加“。”或“,”。 - Mehul

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
小数点后的位数并不重要,但我想将小数位数限制为1,以确保输入的数字始终是有效的双精度浮点数。 - Gabriel Garrett
1
不要在viewDidLoad之外调用它!它会自我调用!请按照我给你的完整类使用。 - Steve Rosenberg
1
当然可以添加删除键或退格案例 - 给我一两分钟 - Steve Rosenberg
1
更新了默认情况。现在应该允许使用退格/删除键。 - Steve Rosenberg
3
由于这种方法只适用于一组非常有限的区域设置,因此它并不是一个好的解决方案。并不是所有用户都使用句点作为小数分隔符,也不是所有用户都使用字符0-9表示数字。如果用户将文本粘贴到文本字段中,这段代码也无法正常工作。 - rmaddy
显示剩余11条评论

7

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? - Hari Narayanan

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
    }

}

6

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


这似乎是最好的答案,但它也接受 0000000000.01,从理论上讲是正确的,但会导致明显的问题。 - Jan L

4

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到9和小数点.

2
它还允许多个小数点(例如123.123.123),因此使其成为无效的十进制数。 - MilanPanchal

3

这是受wye答案启发的,但更加紧凑且适用于我需要数字/小数字段的情况。您可以通过修改正则表达式(删除.?\\d{0,2})来仅接受整数。同样,如果您不想限制小数点后的位数,可以删除该限制(只需将其更改为^\\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.将生成 1.0
  • .1将生成 0.1

2

在Swift 4中改进Naishta的响应,以下是一段代码片段,允许您将文本框长度限制为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
  }

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()
    }
}

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接