私はobjective-cでこれを行う方法について多くのガイドを見つけましたが、これを行うためのよりSwift指向の方法を見たいと思います。
ユーザーが通貨価格を入力するUITextFieldがあります。テキストフィールドは、10進パッドキーボードを呼び出します。ただし、iPadでは、表示されるキーボードには10進数以外の記号がすべて含まれています。
基本的に、キーを1回押すたびに、数字以外や小数点以下1桁を超えるものをフィールドに入力できないようにします。小数を入力する場合、2番目の小数を入力できないようにします。小数が削除された場合は、ユーザーが小数を再度入力できるようにしたいと思います。
これを迅速に適切に行う方法についてのアイデアはありますか?
ここに投稿されているような解決策もあります: ITextFieldを小数点以下1桁に制限しますSwift しかし、関数をどこに配置するか、またはどのように呼び出すべきかわかりません。パラメータにNSRangeを入力しようとすると、範囲を適切に作成していないというエラーが表示されます。
簡単な例を次に示します。
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
}
}
}
これは、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
}
}
すべての回答は「。」を使用します。小数の有効な区切り文字として使用できますが、ローカリゼーションが異なると間違っている可能性があります。
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
}
}
これはYの答えに触発されていますが、もう少しコンパクトで、数値/小数のフィールドが必要な場合に役立ちました。正規表現を変更することで、整数を受け入れるように適応できます(.?\\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を生成します1.
は1.0を生成します.1
は0.1を生成しますスウィフト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桁の小数桁を使用できます。
これが最も簡単な方法です:
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)
}
これがSwift 4の解決策です:
import struct Foundation.CharacterSet
extension String {
var onlyNumbers: String {
let charset = CharacterSet.punctuationCharacters.union(CharacterSet.decimalDigits).inverted
return components(separatedBy: charset).joined()
}
}
テスト済みで動作しますSwift3および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
}
}
これが私が使っているものです。これが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)
}
どちらも短く、明確で、シンプルで、効率的です。 (2番目の方がきれいなようです...意見?)しかし、入力を小数点以下1桁に制限していません...
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
}
Swift 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
}
同じようにします。以下のコードは、複数の.
を防ぐことはできませんが、それ以外の場合は必要なことを行います。必要に応じて拡張します。
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
}
}
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
からの数字と小数点.
が許可されます。
許可された文字と区切り文字をハードコーディングしなくても、より良い結果が得られます。特にセパレーターは、ロケールによって異なる場合があるためです。また、ユーザーがカーソルを移動してテキストを貼り付ける可能性があることに注意する必要があります。これを考慮した検証関数は次のとおりです。
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)
}
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
}
小数点を指定する必要がある場合は、[.,]
を変更してください。
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
}