在 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,以确保输入的数字始终是有效的双精度数。 否 - 在 viewDidLoad 之外。它自称!使用我给你的整个课程。 确定我们可以添加删除键或退格键 - 给我一两分钟 更新了默认情况。这现在应该允许退格/删除键。 这不是一个好的解决方案,因为它只适用于非常有限的语言环境。并非所有用户都将句点用作小数分隔符,也并非所有用户都将字符 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 中将十六进制字符串转换为 CString?