Related
I have found a lot of guides on how to do this in objective-c, but I would like to see a more Swift-oriented way of doing this.
I have a UITextField that a user enters a currency price into. The textfield calls a decimal pad keyboard. However, on the iPad, the keyboard that comes up has a whole range of non-decimal symbols.
Basically, for every single key press, I would like to make it impossible for a non-number or anything beyond a single decimal to be typed into the field. If a decimal is typed, I would like to make it impossible to enter a second decimal. If the decimal is deleted, I'd like to make sure the user can enter a decimal again.
Any ideas on how to properly do this in swift?
I also see solutions like the ones posted here:
Limit UITextField to one decimal point Swift
But I have no idea where to place the functions or how I should call them. Whenever I try to put in NSRange in the parameters, I receive an error that I am not creating a range properly.
Here is a simple example:
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
}
}
}
All of answers use '.' as valid separator for decimals, but in different localisation it's may be wrong.
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
}
}
This takes multiple decimals into account by using an NSScanner to test whether the new string would be numeric:
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
}
}
Swift 2 version of #Steve Rosenberg's solution
If you don't need to limit input to max 2 fractional digits (i.e, "12.34" OK, "12.345" not OK), then remove the 4 lines at the beginning.
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
}
}
}
Swift 3 Implement this UITextFieldDelegate method to prevent user from typing an invalid number:
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
}
It is working with both comma or dot as decimal separator and allows 2 fraction digits.
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)
}
This allows digits from 0 to 9 and decimal point .
This is inspired by wye's answer, but is a bit more compact and has worked for me where I wanted a numeric/decimal field. You can adapt to just accept integers by modifying the regex (take out .?\\d{0,2} leaving you with ^\\d*$). Likewise, if you don't want to restrict the number of digits after the decimal place, you can remove that restriction (just change it to ^\\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
}
This allows the numeric string to be constructed without any rejection of input along the way so, for example, the following are all valid inputs and (newString as NSString).floatValue gives a valid result):
(i.e. the empty string) yields 0.0
. yields 0.0
1. yields 1.0
.1 yields 0.1
Here is the simplest method:
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)
}
Tested and works in Swift 3 and Swift 4, you can also do the checks as below
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
}
}
Improving Naishta's response in Swift 4, here is a snippet that allows you to restrict the textfield length to 10 characters (extra bonus - not requested by post creator) and a single decimal point:
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
}
Here's a Swift 4 solution:
import struct Foundation.CharacterSet
extension String {
var onlyNumbers: String {
let charset = CharacterSet.punctuationCharacters.union(CharacterSet.decimalDigits).inverted
return components(separatedBy: charset).joined()
}
}
Do it the same way. The code below doesn't guard against multiple . but otherwise does what you want. Extend it as you will.
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
}
}
Here is what I use. If this returns false, the caller will remove the last (offending) character with 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))
}
Or you could do it all within the function:
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)
}
Both are short, clear, simple, and efficient. (Seems the second one is cleaner... Opinions?) But they don't limit input to a single decimal point...
Swift 4
Used #SteveRosenberg's answer and wrote this according to my requirements
max number of Integers Numbers is 4 i.e., 9999, and max decimal digits limit is 2. So, max number can be 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
}
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
}
We can do better without hardcoding the allowed characters and the separator. Especially the separator, as it may be different in different locales. Also we need to be aware that a user may move the cursor and paste text. Here is a validation function which takes that into account:
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
}
And the textfield delegate method:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
return Utils.validateDecimalNumberText(for: textField, replacementStringRange: range, string: string)
}
Only numbers.
2 decimal places.
No spaces.
The decimal mark is either a dot or a comma.
If you need to specify the decimal mark, change the [.,].
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
}
Right now I am using this solution without regex. Hope it helps :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
}
SWIFT 3.2 and 4.0
Chis will limit user to two digits after decimal and also will limit them to add one decimal point.
Make sure you set the keyboard type to decimal.
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
}
I want the user to only enter numeric values in a UITextField. On iPhone we can show the numeric keyboard, but on iPad the user can switch to any keyboard.
Is there any way to restrict user to enter only numeric values in a UITextField?
Solution for swift 3.0 and above
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let allowedCharacters = CharacterSet.decimalDigits
let characterSet = CharacterSet(charactersIn: string)
return allowedCharacters.isSuperset(of: characterSet)
}
Here is my 2 Cents. (Tested on Swift 2 Only)
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
let aSet = NSCharacterSet(charactersInString:"0123456789").invertedSet
let compSepByCharInSet = string.componentsSeparatedByCharactersInSet(aSet)
let numberFiltered = compSepByCharInSet.joinWithSeparator("")
return string == numberFiltered
}
This is just a little bit more strict. No decimal point either.
Hope it helps :)
PS: I assumed you looked after the delegate anyway.
Update: Swift 3.0 :
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let aSet = NSCharacterSet(charactersIn:"0123456789").inverted
let compSepByCharInSet = string.components(separatedBy: aSet)
let numberFiltered = compSepByCharInSet.joined(separator: "")
return string == numberFiltered
}
In swift 4.1 and Xcode 9.4.1
Add UITextFieldDelegate to your class
class YourViewController: UIViewController, UITextFieldDelegate
Then write this code in your viewDidLoad()
mobileNoTF.delegate = self
Write this textfield delegate function
//MARK - UITextField Delegates
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
//For mobile numer validation
if textField == mobileNoTF {
let allowedCharacters = CharacterSet(charactersIn:"+0123456789 ")//Here change this characters based on your requirement
let characterSet = CharacterSet(charactersIn: string)
return allowedCharacters.isSuperset(of: characterSet)
}
return true
}
iPhone
In whatever UITextField you're getting these values from, you can specify the kind of keyboard you want to appear when somebody touches inside the text field.
E.G. a numeric-only keyboard.
Like this screenshot:
iPad
The iPad does not support the numeric keyboard, so your options are to either not support the iPad, validate the field post submit, or follow one of the other suggestions here to create same behaviors while running on an iPad.
Swift 2.0
For only allowing numbers and one "." decimal in uitextfield.
func textField(textField: UITextField,shouldChangeCharactersInRange range: NSRange,replacementString string: String) -> Bool
{
let newCharacters = NSCharacterSet(charactersInString: string)
let boolIsNumber = NSCharacterSet.decimalDigitCharacterSet().isSupersetOfSet(newCharacters)
if boolIsNumber == true {
return true
} else {
if string == "." {
let countdots = textField.text!.componentsSeparatedByString(".").count - 1
if countdots == 0 {
return true
} else {
if countdots > 0 && string == "." {
return false
} else {
return true
}
}
} else {
return false
}
}
}
Accept decimal values in text fields with single (.)dot in Swift 3
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let inverseSet = NSCharacterSet(charactersIn:"0123456789").inverted
let components = string.components(separatedBy: inverseSet)
let filtered = components.joined(separator: "")
if filtered == string {
return true
} else {
if string == "." {
let countdots = textField.text!.components(separatedBy:".").count - 1
if countdots == 0 {
return true
}else{
if countdots > 0 && string == "." {
return false
} else {
return true
}
}
}else{
return false
}
}
}
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
// return true if the replacementString only contains numeric characters
let digits = NSCharacterSet.decimalDigitCharacterSet()
for c in string {
if !digits.characterIsMember(c) {
return false
}
}
return true
}
This solution will work even if the user switches keyboards or tries to paste a non-numeric string into the text field.
Make sure to set the delegate property of the appropriate text field.
Extend your view controller like this:
class MyViewController: UIViewController, UITextFieldDelegate
In the viewDidLoad function extend to your text field like this:
myTextField.delegate = self
And then use the following function:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let isNumber = CharacterSet.decimalDigits.isSuperset(of: CharacterSet(charactersIn: string))
let withDecimal = (
string == NumberFormatter().decimalSeparator &&
textField.text?.contains(string) == false
)
return isNumber || withDecimal
}
This will now make sure the user can enter only decimal digits.
Swift 4 +
Accepts Number only
and accepts one separator
Use number formatter
Swift 4.x
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let s = NSString(string: textField.text ?? "").replacingCharacters(in: range, with: string)
guard !s.isEmpty else { return true }
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .none
return numberFormatter.number(from: s)?.intValue != nil
}
Here is a simple solution, you need to connect the event "Editing changed" to this method in your controller
Swift 4
#IBAction func valueChanged(_ sender: UITextField) {
if let last = sender.text?.last {
let zero: Character = "0"
let num: Int = Int(UnicodeScalar(String(last))!.value - UnicodeScalar(String(zero))!.value)
if (num < 0 || num > 9) {
//remove the last character as it is invalid
sender.text?.removeLast()
}
}
}
1st you have to inherit the UITextFieldDelegate class with you own
class
class ViewController: UIViewController, UITextFieldDelegate {
2nd add an IBOutlet
#IBOutlet weak var firstName: UITextField!
3rd you have to assure this object is using
override func viewDidLoad() {
super.viewDidLoad()
firstName.delegate = self
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if textField == firstName {
let allowedCharacters = "1234567890"
let allowedCharacterSet = CharacterSet(charactersIn: allowedCharacters)
let typedCharacterSet = CharacterSet(charactersIn: string)
let alphabet = allowedCharacterSet.isSuperset(of: typedCharacterSet)
return alphabet
}
}
While most of these solutions will work, be aware that in some localisations a decimals are separated with a "," and not a "."
The cleaner way to do this would be
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
let decimalCharacter = NSNumberFormatter().decimalSeparator
let characterSet = NSMutableCharacterSet.decimalDigitCharacterSet()
characterSet.addCharactersInString(decimalCharacter)
return replacementString.rangeOfCharacterFromSet(characterSet.invertedSet) == nil
}
Tested in swift 3.0
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool
{
let numberOnly = NSCharacterSet.init(charactersIn: "0123456789")
let stringFromTextField = NSCharacterSet.init(charactersIn: string)
let strValid = numberOnly.isSuperset(of: stringFromTextField as CharacterSet)
return strValid
}
Here's an cleaner solution:
guard CharacterSet(charactersIn: "123456789").isSuperset(of: CharacterSet(charactersIn: string)) else {
return false
}
return true
For decimals just add ., example 123456789.
Set KeyboardType Property :- Number Pad
TextField Delegate please write below code
func textField(_ textField: UITextField, shouldChangeCharactersIn
range: NSRange, replacementString string: String) -> Bool {
if textField.text?.count == 0 && string == "0" {
return false
}
return string == string.filter("0123456789".contains)
}
Number should not start from 0 and entered number +ve.
//instead of these you can simply change your keyboard to number type
yourtextfield.keyboardType = UIKeyboardType.numberPad
I had actually done this when working through the Big Nerd Ranch book, my solution is:
func textField(textField: UITextField,
shouldChangeCharactersInRange range: NSRange,
replacementString string: String) -> Bool {
let newCharacters = NSCharacterSet(charactersInString: string)
return NSCharacterSet.decimalDigitCharacterSet().isSupersetOfSet(newCharacters)
}
this only allows the numbers 0-9, to allow the "." as well is more complicated as you can only allow one "."
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
if let numRange = string.rangeOfCharacterFromSet(NSCharacterSet.letterCharacterSet()) {
return false
} else {
return true
}
}
To allow only numbers and just one decimal operator, you can use this solution:
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
let isNumber = NSCharacterSet.decimalDigitCharacterSet().isSupersetOfSet(NSCharacterSet(charactersInString: string))
return isNumber || (string == NSNumberFormatter().decimalSeparator && textField.text?.containsString(string) == false)
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool
{
let textString = (textField.text! as NSString).replacingCharacters(in: range, with: string)
if textField == self.phoneTextField && string.characters.count > 0{
let numberOnly = NSCharacterSet.decimalDigits
let strValid = numberOnly.contains(UnicodeScalar.init(string)!)
return strValid && textString.characters.count <= 10
}
return true
}
in above code is working in swift 3
NSCharacterSet.decimalDigits
You are also use letters only
NSCharacterSet.Letters
and uppercase,Lowercaseand,alphanumerics,whitespaces
is used same code
or See the Link
I think you can force change the keyboard type by implementing UITextInputTraits protocol, optional var keyboardType
//class ViewController: UIViewController, UITextInputTraits {
#IBOutlet weak var textFieldKeyboardType: UITextField!{
didSet{
textFieldKeyboardType.keyboardType = UIKeyboardType.NumberPad
}
}
var keyboardType: UIKeyboardType {
get{
return textFieldKeyboardType.keyboardType
}
set{
if newValue != UIKeyboardType.NumberPad{
self.keyboardType = UIKeyboardType.NumberPad
}
}
}
This is a more readable version that will do "0-9" plus ".":
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
let existingTextHasDecimal = textField.text?.rangeOfString(".")
let replacementTextHasDecimal = string.rangeOfString(".")
let replacementTextAllCharacters = NSCharacterSet(charactersInString: string)
let replacementTextOnlyDigits = NSCharacterSet.decimalDigitCharacterSet().isSupersetOfSet(replacementTextAllCharacters)
if replacementTextHasDecimal != nil && existingTextHasDecimal != nil {
return false
}else{
if replacementTextOnlyDigits == true {
return true
}else if replacementTextHasDecimal != nil{
return true
}else{
return false
}
}
}
As if there aren't enough answers, here's mine. I think every example allowed for decimal separators is flawed in either localization, backspaces, or copy/paste.
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
if string.isEmpty {return true} //allow for backspace
let decimalSeparator = NSNumberFormatter().decimalSeparator ?? "."
let validChars = NSMutableCharacterSet(charactersInString: decimalSeparator)
validChars.formUnionWithCharacterSet(NSCharacterSet.decimalDigitCharacterSet())
if validChars.isSupersetOfSet(NSCharacterSet(charactersInString: string)){
switch string.componentsSeparatedByString(decimalSeparator).count-1 {
case 0: //no decimals
return true
case 1: //if adding decimal, only allow if no existing decimal
if let existingText = textField.text{
return existingText.componentsSeparatedByString(decimalSeparator).count <= 1
}
else {return true}
default: //invalid decimals
return false
}
}
return false
}
func isValidNumber(str:String) -> Bool{
if str.isEmpty {
return false
}
let newChar = NSCharacterSet(charactersInString: str)
let boolValid = NSCharacterSet.decimalDigitCharacterSet().isSupersetOfSet(newChar)
if boolValid{
return true
}else{
let lst = str.componentsSeparatedByString(".")
let newStr = lst.joinWithSeparator("")
let currentChar = NSCharacterSet(charactersInString: newStr)
if lst.count == 2 && !lst.contains("") && NSCharacterSet.decimalDigitCharacterSet().isSupersetOfSet(currentChar){
return true
}
return false
}
}
Put this function in your "Submit" or "Save" method if there is one.
The following is the code I used in Swift 3.0 adapted from Mr H's code. Differences are because:
a) Delegate function declaration has changed in Swift 3.0. New declaration here
b) NSCharacterSet declaration has changed.
func textField(_ shouldChangeCharactersIntextField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool
{
let inverseSet = NSCharacterSet(charactersIn:"0123456789").inverted
let components = string.components(separatedBy: inverseSet)
let filtered = components.joined(separator: "")
return string == filtered
}
I have edited Raj Joshi's version to allow one dot or one comma:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let inverseSet = CharacterSet(charactersIn:"0123456789").inverted
let components = string.components(separatedBy: inverseSet)
let filtered = components.joined(separator: "")
if filtered == string {
return true
} else {
if string == "." || string == "," {
let countDots = textField.text!.components(separatedBy:".").count - 1
let countCommas = textField.text!.components(separatedBy:",").count - 1
if countDots == 0 && countCommas == 0 {
return true
} else {
return false
}
} else {
return false
}
}
}
You can use this code if you want to allow decimal separator and/or negative numbers.
But this code allows example: "34." (decimal separator at the end) while changing text. So you have to add some code example: textFieldShouldReturn or textFieldShouldEndEditing delegate functions.
The code written in Swift 4 but I assueme this is compatible with Swift 3.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
guard let text = textField.text else {
return true
}
let replaced = (text as NSString).replacingCharacters(in: range, with: string)
let decimalSeparator = NSLocale.current.decimalSeparator ?? ""
// When user wants to delete las character
if replaced == "" || replaced == "-" || replaced == "-0" {
textField.text = "0"
return false
}
// When text contains 0 before replace except "0."
if replaced != "0" + decimalSeparator && replaced.hasPrefix("0") && text.underestimatedCount == 1 {
textField.text = replaced.substring(from: replaced.index(after: replaced.startIndex))
return false
}
// When user wants to delete minus sign
if text.hasPrefix("-") && text.substring(from: text.index(after: text.startIndex)) == replaced {
return false
}
// When user wants to delete before decimal separator
if replaced.hasPrefix(decimalSeparator) || replaced.hasPrefix("-" + decimalSeparator) {
return false
}
// When user wants to add zero the beginning of number... but allowing "0." or "-0." numbers
let testReplaced = replaced.hasPrefix("-") ? replaced.substring(from: replaced.index(after: replaced.startIndex)) : replaced
if testReplaced.count >= 2 && testReplaced.hasPrefix("0") && !testReplaced.hasPrefix("0" + decimalSeparator) {
return false
}
// Every other cases
let allowDecimal = self.allowFloat ? (decimalSeparator == "." ? "\\.?" : decimalSeparator + "?") : ""
let allowSign = self.allowSigned ? "-?" : ""
let pattern = "\(allowSign)[0-9]+\(allowDecimal)([0-9]+)?"
do {
let regexRange = (replaced as NSString).range(of: replaced)
let regex = try NSRegularExpression(pattern: pattern, options: [])
let matches = regex.matches(in: replaced, options: [], range: regexRange)
return matches.count == 1 && matches.first!.range == regexRange
}
catch {}
return false
}
If you don't want to allow decimal or negative numbers you have to replace tow variable with next line
let allowDecimal = ""
let allowSign = ""
For allow some charactors
func CheckAddress(string:String) -> Bool {
let numberOnly = NSCharacterSet.init(charactersIn: "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-#,&#/")
let stringFromTextField = NSCharacterSet.init(charactersIn: string)
return numberOnly.isSuperset(of: stringFromTextField as CharacterSet)
}
print("\(CheckAddress(string: "123"))") //True
print("\(CheckAddress(string: "asdf-"))") //True
print("\(CheckAddress(string: "asd123$"))") //false
The following solution has two benefits:
It is a one line code
It restricts the input so that the overall text in the input field is a valid number. Other solutions restricts the digits to valid numbers but this results in the user is able to enter "4...5"
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
return NumberFormatter().numberFrom(text: (textField.text ?? "") + string) != nil
}
Swift 2.0
func textField(textField: UITextField,
shouldChangeCharactersInRange range: NSRange,
replacementString string: String) -> Bool {
let inverseSet = NSCharacterSet(charactersInString:"0123456789").invertedSet
let components = string.componentsSeparatedByCharactersInSet(inverseSet)
let filtered = components.joinWithSeparator("")
return string == filtered
}
I have an iOS Xcode 7.3 Swift2 project I'm working on. It has different UITextFields that are limited to 3 digits, specifically only numbers. They are assigned to the UITextFieldDelegate and it's working well.
Here is where I limit them:
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
guard let text = textField.text else { return true }
let newLength = text.characters.count + string.characters.count - range.length
let limitLength = 3
if newLength > limitLength {
return false
}
let numberOnly = NSCharacterSet.init(charactersInString: "0123456789")
let stringFromTextField = NSCharacterSet.init(charactersInString: string)
let strValid = numberOnly.isSupersetOfSet(stringFromTextField)
return strValid
}
However, some of the UITextFields need to be limited to numbers still AND also limited to a single digit, how can I institute this in the section above, only for those specific UITextFields?
The names of the UITextFields that need to be single digits are:
widthInches
lengthInches
I tried placing this after the first guard section with no luck:
guard let text2 = widthInches.text else { return true }
let newLength2 = text2.characters.count + string.characters.count - range.length
let limitLength2 = 3
if newLength2 > limitLength2 {
return false
}
You can also try this code for limit textfield
actually i am using here textfield tag. Because custom textfield.
If you using custom textfield like TextfieldEffect in this condition tag will help you for limit of Textfield.
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool{
if textField.tag == txtCountryCode.tag{
let maxLength = 4
let currentString: NSString = textField.text!
let newString: NSString =
currentString.stringByReplacingCharactersInRange(range, withString: string)
return newString.length <= maxLength
}
if textField.tag == txtMobileNumber.tag{
let maxLength = 10
let currentString: NSString = textField.text!
let newString: NSString =
currentString.stringByReplacingCharactersInRange(range, withString: string)
return newString.length <= maxLength
}
return true
}
I hope this will help you.
The function shouldChangeCharactersInRange passes in the particular textField as one of its parameters. You can look at that and see if it points to the same instance as the ones you want to shorten, like this:
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
guard let text = textField.text else { return true }
var limitLength = 3
if textField == widthInches || textField == lengthInches {
limitLength = 1
}
let newLength = text.characters.count + string.characters.count - range.length
if newLength > limitLength {
return false
}
let numberOnly = NSCharacterSet.init(charactersInString: "0123456789")
let stringFromTextField = NSCharacterSet.init(charactersInString: string)
let strValid = numberOnly.isSupersetOfSet(stringFromTextField)
return strValid
}
Assuming all other requirements are the same (numbers only) this will do the trick.
There are other ways, for example - you could subclass UITextField and add a limitLength field, then use that field in the delegate, but that's probably overkill for just 2 exceptions.
Hello in your func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool the textField param is the textField that has trigger this event so you can check with yours textfields objects and if are equal to one of them then make a different behavior
I hope this helps you,
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
return (textField.text?.utf16.count ?? 0) + string.utf16.count - range.length <= TEXT_FIELD_LIMIT
}
This counts the number of characters based on UTF-16 representation, as range.length is given in UTF-16 base. If you need to count the number of characters in other ways, the expression may get longer. If you want only numbers to be input use textField.keyboardType = UIKeyboardTypeDecimalPad . If you want specific textFields then add tags and compare them and if they are equal you can implement your specific code for that.
Check this link for detailed answer :
http://www.globalnerdy.com/2016/05/24/a-better-way-to-program-ios-text-fields-that-have-maximum-lengths-and-accept-or-reject-specific-characters/
update for swift 3 add this class and call it TextField.swift. it will add the limit input on the storyboard.
import UIKit
private var maxLengths = [UITextField: Int]()
extension UITextField {
#IBInspectable var maxLength: Int {
get {
guard let length = maxLengths[self] else {
return Int.max
}
return length
}
set {
maxLengths[self] = newValue
// Any text field with a set max length will call the limitLength
// method any time it's edited (i.e. when the user adds, removes,
// cuts, or pastes characters to/from the text field).
addTarget(
self,
action: #selector(limitLength),
for: UIControlEvents.editingChanged
)
}
}
func limitLength(textField: UITextField) {
guard let prospectiveText = textField.text,
prospectiveText.characters.count > maxLength else {
return
}
// If the change in the text field's contents will exceed its maximum
length,
// allow only the first [maxLength] characters of the resulting text.
let selection = selectedTextRange
// text = prospectiveText.substring(with:Range<String.Index>
(prospectiveText.startIndex ..< prospectiveText.index(after: maxLength))
let s = prospectiveText
// Get range 4 places from the start, and 6 from the end.
let c = s.characters;
let r = c.index(c.startIndex, offsetBy: 0)..<c.index(c.endIndex, offsetBy: maxLength - c.count)
text = s[r]
// Access the string by the range.
selectedTextRange = selection
}
}
or download here - >TextField.swift
I want the user to only enter numeric values in a UITextField. On iPhone we can show the numeric keyboard, but on iPad the user can switch to any keyboard.
Is there any way to restrict user to enter only numeric values in a UITextField?
Solution for swift 3.0 and above
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let allowedCharacters = CharacterSet.decimalDigits
let characterSet = CharacterSet(charactersIn: string)
return allowedCharacters.isSuperset(of: characterSet)
}
Here is my 2 Cents. (Tested on Swift 2 Only)
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
let aSet = NSCharacterSet(charactersInString:"0123456789").invertedSet
let compSepByCharInSet = string.componentsSeparatedByCharactersInSet(aSet)
let numberFiltered = compSepByCharInSet.joinWithSeparator("")
return string == numberFiltered
}
This is just a little bit more strict. No decimal point either.
Hope it helps :)
PS: I assumed you looked after the delegate anyway.
Update: Swift 3.0 :
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let aSet = NSCharacterSet(charactersIn:"0123456789").inverted
let compSepByCharInSet = string.components(separatedBy: aSet)
let numberFiltered = compSepByCharInSet.joined(separator: "")
return string == numberFiltered
}
In swift 4.1 and Xcode 9.4.1
Add UITextFieldDelegate to your class
class YourViewController: UIViewController, UITextFieldDelegate
Then write this code in your viewDidLoad()
mobileNoTF.delegate = self
Write this textfield delegate function
//MARK - UITextField Delegates
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
//For mobile numer validation
if textField == mobileNoTF {
let allowedCharacters = CharacterSet(charactersIn:"+0123456789 ")//Here change this characters based on your requirement
let characterSet = CharacterSet(charactersIn: string)
return allowedCharacters.isSuperset(of: characterSet)
}
return true
}
iPhone
In whatever UITextField you're getting these values from, you can specify the kind of keyboard you want to appear when somebody touches inside the text field.
E.G. a numeric-only keyboard.
Like this screenshot:
iPad
The iPad does not support the numeric keyboard, so your options are to either not support the iPad, validate the field post submit, or follow one of the other suggestions here to create same behaviors while running on an iPad.
Swift 2.0
For only allowing numbers and one "." decimal in uitextfield.
func textField(textField: UITextField,shouldChangeCharactersInRange range: NSRange,replacementString string: String) -> Bool
{
let newCharacters = NSCharacterSet(charactersInString: string)
let boolIsNumber = NSCharacterSet.decimalDigitCharacterSet().isSupersetOfSet(newCharacters)
if boolIsNumber == true {
return true
} else {
if string == "." {
let countdots = textField.text!.componentsSeparatedByString(".").count - 1
if countdots == 0 {
return true
} else {
if countdots > 0 && string == "." {
return false
} else {
return true
}
}
} else {
return false
}
}
}
Accept decimal values in text fields with single (.)dot in Swift 3
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let inverseSet = NSCharacterSet(charactersIn:"0123456789").inverted
let components = string.components(separatedBy: inverseSet)
let filtered = components.joined(separator: "")
if filtered == string {
return true
} else {
if string == "." {
let countdots = textField.text!.components(separatedBy:".").count - 1
if countdots == 0 {
return true
}else{
if countdots > 0 && string == "." {
return false
} else {
return true
}
}
}else{
return false
}
}
}
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
// return true if the replacementString only contains numeric characters
let digits = NSCharacterSet.decimalDigitCharacterSet()
for c in string {
if !digits.characterIsMember(c) {
return false
}
}
return true
}
This solution will work even if the user switches keyboards or tries to paste a non-numeric string into the text field.
Make sure to set the delegate property of the appropriate text field.
Extend your view controller like this:
class MyViewController: UIViewController, UITextFieldDelegate
In the viewDidLoad function extend to your text field like this:
myTextField.delegate = self
And then use the following function:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let isNumber = CharacterSet.decimalDigits.isSuperset(of: CharacterSet(charactersIn: string))
let withDecimal = (
string == NumberFormatter().decimalSeparator &&
textField.text?.contains(string) == false
)
return isNumber || withDecimal
}
This will now make sure the user can enter only decimal digits.
Swift 4 +
Accepts Number only
and accepts one separator
Use number formatter
Swift 4.x
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let s = NSString(string: textField.text ?? "").replacingCharacters(in: range, with: string)
guard !s.isEmpty else { return true }
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .none
return numberFormatter.number(from: s)?.intValue != nil
}
Here is a simple solution, you need to connect the event "Editing changed" to this method in your controller
Swift 4
#IBAction func valueChanged(_ sender: UITextField) {
if let last = sender.text?.last {
let zero: Character = "0"
let num: Int = Int(UnicodeScalar(String(last))!.value - UnicodeScalar(String(zero))!.value)
if (num < 0 || num > 9) {
//remove the last character as it is invalid
sender.text?.removeLast()
}
}
}
1st you have to inherit the UITextFieldDelegate class with you own
class
class ViewController: UIViewController, UITextFieldDelegate {
2nd add an IBOutlet
#IBOutlet weak var firstName: UITextField!
3rd you have to assure this object is using
override func viewDidLoad() {
super.viewDidLoad()
firstName.delegate = self
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if textField == firstName {
let allowedCharacters = "1234567890"
let allowedCharacterSet = CharacterSet(charactersIn: allowedCharacters)
let typedCharacterSet = CharacterSet(charactersIn: string)
let alphabet = allowedCharacterSet.isSuperset(of: typedCharacterSet)
return alphabet
}
}
While most of these solutions will work, be aware that in some localisations a decimals are separated with a "," and not a "."
The cleaner way to do this would be
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
let decimalCharacter = NSNumberFormatter().decimalSeparator
let characterSet = NSMutableCharacterSet.decimalDigitCharacterSet()
characterSet.addCharactersInString(decimalCharacter)
return replacementString.rangeOfCharacterFromSet(characterSet.invertedSet) == nil
}
Tested in swift 3.0
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool
{
let numberOnly = NSCharacterSet.init(charactersIn: "0123456789")
let stringFromTextField = NSCharacterSet.init(charactersIn: string)
let strValid = numberOnly.isSuperset(of: stringFromTextField as CharacterSet)
return strValid
}
Here's an cleaner solution:
guard CharacterSet(charactersIn: "123456789").isSuperset(of: CharacterSet(charactersIn: string)) else {
return false
}
return true
For decimals just add ., example 123456789.
Set KeyboardType Property :- Number Pad
TextField Delegate please write below code
func textField(_ textField: UITextField, shouldChangeCharactersIn
range: NSRange, replacementString string: String) -> Bool {
if textField.text?.count == 0 && string == "0" {
return false
}
return string == string.filter("0123456789".contains)
}
Number should not start from 0 and entered number +ve.
//instead of these you can simply change your keyboard to number type
yourtextfield.keyboardType = UIKeyboardType.numberPad
I had actually done this when working through the Big Nerd Ranch book, my solution is:
func textField(textField: UITextField,
shouldChangeCharactersInRange range: NSRange,
replacementString string: String) -> Bool {
let newCharacters = NSCharacterSet(charactersInString: string)
return NSCharacterSet.decimalDigitCharacterSet().isSupersetOfSet(newCharacters)
}
this only allows the numbers 0-9, to allow the "." as well is more complicated as you can only allow one "."
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
if let numRange = string.rangeOfCharacterFromSet(NSCharacterSet.letterCharacterSet()) {
return false
} else {
return true
}
}
To allow only numbers and just one decimal operator, you can use this solution:
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
let isNumber = NSCharacterSet.decimalDigitCharacterSet().isSupersetOfSet(NSCharacterSet(charactersInString: string))
return isNumber || (string == NSNumberFormatter().decimalSeparator && textField.text?.containsString(string) == false)
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool
{
let textString = (textField.text! as NSString).replacingCharacters(in: range, with: string)
if textField == self.phoneTextField && string.characters.count > 0{
let numberOnly = NSCharacterSet.decimalDigits
let strValid = numberOnly.contains(UnicodeScalar.init(string)!)
return strValid && textString.characters.count <= 10
}
return true
}
in above code is working in swift 3
NSCharacterSet.decimalDigits
You are also use letters only
NSCharacterSet.Letters
and uppercase,Lowercaseand,alphanumerics,whitespaces
is used same code
or See the Link
I think you can force change the keyboard type by implementing UITextInputTraits protocol, optional var keyboardType
//class ViewController: UIViewController, UITextInputTraits {
#IBOutlet weak var textFieldKeyboardType: UITextField!{
didSet{
textFieldKeyboardType.keyboardType = UIKeyboardType.NumberPad
}
}
var keyboardType: UIKeyboardType {
get{
return textFieldKeyboardType.keyboardType
}
set{
if newValue != UIKeyboardType.NumberPad{
self.keyboardType = UIKeyboardType.NumberPad
}
}
}
This is a more readable version that will do "0-9" plus ".":
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
let existingTextHasDecimal = textField.text?.rangeOfString(".")
let replacementTextHasDecimal = string.rangeOfString(".")
let replacementTextAllCharacters = NSCharacterSet(charactersInString: string)
let replacementTextOnlyDigits = NSCharacterSet.decimalDigitCharacterSet().isSupersetOfSet(replacementTextAllCharacters)
if replacementTextHasDecimal != nil && existingTextHasDecimal != nil {
return false
}else{
if replacementTextOnlyDigits == true {
return true
}else if replacementTextHasDecimal != nil{
return true
}else{
return false
}
}
}
As if there aren't enough answers, here's mine. I think every example allowed for decimal separators is flawed in either localization, backspaces, or copy/paste.
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
if string.isEmpty {return true} //allow for backspace
let decimalSeparator = NSNumberFormatter().decimalSeparator ?? "."
let validChars = NSMutableCharacterSet(charactersInString: decimalSeparator)
validChars.formUnionWithCharacterSet(NSCharacterSet.decimalDigitCharacterSet())
if validChars.isSupersetOfSet(NSCharacterSet(charactersInString: string)){
switch string.componentsSeparatedByString(decimalSeparator).count-1 {
case 0: //no decimals
return true
case 1: //if adding decimal, only allow if no existing decimal
if let existingText = textField.text{
return existingText.componentsSeparatedByString(decimalSeparator).count <= 1
}
else {return true}
default: //invalid decimals
return false
}
}
return false
}
func isValidNumber(str:String) -> Bool{
if str.isEmpty {
return false
}
let newChar = NSCharacterSet(charactersInString: str)
let boolValid = NSCharacterSet.decimalDigitCharacterSet().isSupersetOfSet(newChar)
if boolValid{
return true
}else{
let lst = str.componentsSeparatedByString(".")
let newStr = lst.joinWithSeparator("")
let currentChar = NSCharacterSet(charactersInString: newStr)
if lst.count == 2 && !lst.contains("") && NSCharacterSet.decimalDigitCharacterSet().isSupersetOfSet(currentChar){
return true
}
return false
}
}
Put this function in your "Submit" or "Save" method if there is one.
The following is the code I used in Swift 3.0 adapted from Mr H's code. Differences are because:
a) Delegate function declaration has changed in Swift 3.0. New declaration here
b) NSCharacterSet declaration has changed.
func textField(_ shouldChangeCharactersIntextField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool
{
let inverseSet = NSCharacterSet(charactersIn:"0123456789").inverted
let components = string.components(separatedBy: inverseSet)
let filtered = components.joined(separator: "")
return string == filtered
}
I have edited Raj Joshi's version to allow one dot or one comma:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let inverseSet = CharacterSet(charactersIn:"0123456789").inverted
let components = string.components(separatedBy: inverseSet)
let filtered = components.joined(separator: "")
if filtered == string {
return true
} else {
if string == "." || string == "," {
let countDots = textField.text!.components(separatedBy:".").count - 1
let countCommas = textField.text!.components(separatedBy:",").count - 1
if countDots == 0 && countCommas == 0 {
return true
} else {
return false
}
} else {
return false
}
}
}
You can use this code if you want to allow decimal separator and/or negative numbers.
But this code allows example: "34." (decimal separator at the end) while changing text. So you have to add some code example: textFieldShouldReturn or textFieldShouldEndEditing delegate functions.
The code written in Swift 4 but I assueme this is compatible with Swift 3.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
guard let text = textField.text else {
return true
}
let replaced = (text as NSString).replacingCharacters(in: range, with: string)
let decimalSeparator = NSLocale.current.decimalSeparator ?? ""
// When user wants to delete las character
if replaced == "" || replaced == "-" || replaced == "-0" {
textField.text = "0"
return false
}
// When text contains 0 before replace except "0."
if replaced != "0" + decimalSeparator && replaced.hasPrefix("0") && text.underestimatedCount == 1 {
textField.text = replaced.substring(from: replaced.index(after: replaced.startIndex))
return false
}
// When user wants to delete minus sign
if text.hasPrefix("-") && text.substring(from: text.index(after: text.startIndex)) == replaced {
return false
}
// When user wants to delete before decimal separator
if replaced.hasPrefix(decimalSeparator) || replaced.hasPrefix("-" + decimalSeparator) {
return false
}
// When user wants to add zero the beginning of number... but allowing "0." or "-0." numbers
let testReplaced = replaced.hasPrefix("-") ? replaced.substring(from: replaced.index(after: replaced.startIndex)) : replaced
if testReplaced.count >= 2 && testReplaced.hasPrefix("0") && !testReplaced.hasPrefix("0" + decimalSeparator) {
return false
}
// Every other cases
let allowDecimal = self.allowFloat ? (decimalSeparator == "." ? "\\.?" : decimalSeparator + "?") : ""
let allowSign = self.allowSigned ? "-?" : ""
let pattern = "\(allowSign)[0-9]+\(allowDecimal)([0-9]+)?"
do {
let regexRange = (replaced as NSString).range(of: replaced)
let regex = try NSRegularExpression(pattern: pattern, options: [])
let matches = regex.matches(in: replaced, options: [], range: regexRange)
return matches.count == 1 && matches.first!.range == regexRange
}
catch {}
return false
}
If you don't want to allow decimal or negative numbers you have to replace tow variable with next line
let allowDecimal = ""
let allowSign = ""
For allow some charactors
func CheckAddress(string:String) -> Bool {
let numberOnly = NSCharacterSet.init(charactersIn: "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-#,&#/")
let stringFromTextField = NSCharacterSet.init(charactersIn: string)
return numberOnly.isSuperset(of: stringFromTextField as CharacterSet)
}
print("\(CheckAddress(string: "123"))") //True
print("\(CheckAddress(string: "asdf-"))") //True
print("\(CheckAddress(string: "asd123$"))") //false
The following solution has two benefits:
It is a one line code
It restricts the input so that the overall text in the input field is a valid number. Other solutions restricts the digits to valid numbers but this results in the user is able to enter "4...5"
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
return NumberFormatter().numberFrom(text: (textField.text ?? "") + string) != nil
}
Swift 2.0
func textField(textField: UITextField,
shouldChangeCharactersInRange range: NSRange,
replacementString string: String) -> Bool {
let inverseSet = NSCharacterSet(charactersInString:"0123456789").invertedSet
let components = string.componentsSeparatedByCharactersInSet(inverseSet)
let filtered = components.joinWithSeparator("")
return string == filtered
}
I have found a lot of guides on how to do this in objective-c, but I would like to see a more Swift-oriented way of doing this.
I have a UITextField that a user enters a currency price into. The textfield calls a decimal pad keyboard. However, on the iPad, the keyboard that comes up has a whole range of non-decimal symbols.
Basically, for every single key press, I would like to make it impossible for a non-number or anything beyond a single decimal to be typed into the field. If a decimal is typed, I would like to make it impossible to enter a second decimal. If the decimal is deleted, I'd like to make sure the user can enter a decimal again.
Any ideas on how to properly do this in swift?
I also see solutions like the ones posted here:
Limit UITextField to one decimal point Swift
But I have no idea where to place the functions or how I should call them. Whenever I try to put in NSRange in the parameters, I receive an error that I am not creating a range properly.
Here is a simple example:
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
}
}
}
All of answers use '.' as valid separator for decimals, but in different localisation it's may be wrong.
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
}
}
This takes multiple decimals into account by using an NSScanner to test whether the new string would be numeric:
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
}
}
Swift 2 version of #Steve Rosenberg's solution
If you don't need to limit input to max 2 fractional digits (i.e, "12.34" OK, "12.345" not OK), then remove the 4 lines at the beginning.
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
}
}
}
Swift 3 Implement this UITextFieldDelegate method to prevent user from typing an invalid number:
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
}
It is working with both comma or dot as decimal separator and allows 2 fraction digits.
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)
}
This allows digits from 0 to 9 and decimal point .
This is inspired by wye's answer, but is a bit more compact and has worked for me where I wanted a numeric/decimal field. You can adapt to just accept integers by modifying the regex (take out .?\\d{0,2} leaving you with ^\\d*$). Likewise, if you don't want to restrict the number of digits after the decimal place, you can remove that restriction (just change it to ^\\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
}
This allows the numeric string to be constructed without any rejection of input along the way so, for example, the following are all valid inputs and (newString as NSString).floatValue gives a valid result):
(i.e. the empty string) yields 0.0
. yields 0.0
1. yields 1.0
.1 yields 0.1
Here is the simplest method:
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)
}
Tested and works in Swift 3 and Swift 4, you can also do the checks as below
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
}
}
Improving Naishta's response in Swift 4, here is a snippet that allows you to restrict the textfield length to 10 characters (extra bonus - not requested by post creator) and a single decimal point:
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
}
Here's a Swift 4 solution:
import struct Foundation.CharacterSet
extension String {
var onlyNumbers: String {
let charset = CharacterSet.punctuationCharacters.union(CharacterSet.decimalDigits).inverted
return components(separatedBy: charset).joined()
}
}
Do it the same way. The code below doesn't guard against multiple . but otherwise does what you want. Extend it as you will.
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
}
}
Here is what I use. If this returns false, the caller will remove the last (offending) character with 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))
}
Or you could do it all within the function:
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)
}
Both are short, clear, simple, and efficient. (Seems the second one is cleaner... Opinions?) But they don't limit input to a single decimal point...
Swift 4
Used #SteveRosenberg's answer and wrote this according to my requirements
max number of Integers Numbers is 4 i.e., 9999, and max decimal digits limit is 2. So, max number can be 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
}
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
}
We can do better without hardcoding the allowed characters and the separator. Especially the separator, as it may be different in different locales. Also we need to be aware that a user may move the cursor and paste text. Here is a validation function which takes that into account:
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
}
And the textfield delegate method:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
return Utils.validateDecimalNumberText(for: textField, replacementStringRange: range, string: string)
}
Only numbers.
2 decimal places.
No spaces.
The decimal mark is either a dot or a comma.
If you need to specify the decimal mark, change the [.,].
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
}
Right now I am using this solution without regex. Hope it helps :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
}
SWIFT 3.2 and 4.0
Chis will limit user to two digits after decimal and also will limit them to add one decimal point.
Make sure you set the keyboard type to decimal.
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
}