I'm building a simple Welcome page that has a Name & Age text input field. I was able to successfully limit the character input count on the name field, but using the same logic on the age field has not worked at all. Thoughts?
class WelcomeViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var nameTextField: UITextField!
#IBOutlet weak var ageTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
nameTextField.delegate = self
ageTextField.delegate = self
}
// Character Count Code UITextField
func textField(_ nameTextField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
// get the current text, or use an empty string if that failed
let currentText = nameTextField.text ?? ""
// attempt to read the range they are trying to change, or exit if we can't
guard let stringRange = Range(range, in: currentText) else { return false }
// add their new text to the existing text
let updatedText = currentText.replacingCharacters(in: stringRange, with: string)
// make sure the result is under # characters
return updatedText.count <= 30
}
func textField2(_ ageTextField: UITextField, shouldChangeCharactersIn range2: NSRange, replacementString string2: String) -> Bool {
// get the current text, or use an empty string if that failed
let currentText2 = ageTextField.text ?? ""
// attempt to read the range they are trying to change, or exit if we can't
guard let stringRange2 = Range(range2, in: currentText2) else { return false }
// add their new text to the existing text
let updatedText2 = currentText2.replacingCharacters(in: stringRange2, with: string2)
// make sure the result is under # characters
return updatedText2.count <= 2
}
The delegate method has a specific signature (textField(_:shouldChangeCharactersIn:replacementString:)) that is called by the system. Your first function uses that pattern.
Your second one, currently, does not, since you've named it textField2 -- the system has no reason to know to call something starting with that prefix.
Instead, you should probably use a single function and then have it behave differently based on which UITextField is sent in to the first parameter:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if textField == nameTextField {
//content
}
if textField == ageTextField {
//content
}
}
This could obviously be an else if, or even just an else if you stay limited to 2 text fields. Within the if block, you could either call out to a separate, specialized function, or keep all of your logic within the if block.
Example with switch:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
switch textfield {
case nameTextField:
//content
case ageTextField:
//content
default:
return true
}
}
Related
In iOS 13, when implementing shouldChangeCharactersIn via the UITextfieldDelegate, the application crashes when using the swiping keyboard.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if let text = textField.text as NSString? {
let txtAfterUpdate = text.replacingCharacters(in: range, with: string)
textField.text = txtAfterUpdate
}
return false
}
Is this an Apple bug?
I was able to reproduce this - if you mutate the state of the text on a UITextField during swipe entry - and only during swipe entry, it'll attempt to reinsert the swiped content (even if you return false), which retriggers your delegate event, which kicks off the recursive cycle.
It's a bit of a hack but you could catch it with something like
private var lastEntry: String?
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if string.count > 1 && string == lastEntry { // implies we're swiping or pasting
print("Caught unwanted recursion")
return
}
lastEntry = string
if let text = textField.text as NSString? {
let txtAfterUpdate = text.replacingCharacters(in: range, with: string)
textField.text = txtAfterUpdate
}
return false
}
It'll stop users from pasting/swiping the same thing twice in a row, but at least it'll let them swipe while Apple fixes their problem.
I used UIPasteboard to identify when the user is pasting and then leave the text as the user entered using the swipe like this:
public func textField(_ textField: UITextField,
shouldChangeCharactersIn range: NSRange,
replacementString string: String) -> Bool {
//check if the user used swipe keyboard
if string.count > 1 && string != UIPasteboard.general.string ?? "" {
return true
}
//do the text treatment
return false
}
I also realized that the TextField only accepts static strings when using swipe keyboard.
Hope it Helps.
Before setting text you can reset delegate and after set it to self again.
But this solution has one problem if textfield is empty - text will be doubled.
Му code example:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let currentText: String = textField.text ?? ""
if #available(iOS 13, *) {
textField.delegate = nil
let resultText = editedText
textField.text = resultText
if currentText.isEmpty, textField.text != resultText {
textField.text = resultText
}
textField.delegate = self
} else {
textField.text = input.result
}
return false
}
I have a requirement to show "#" instead of bullets for password field.
But as there is no default option available for it in UITextField.
I have tried to write custom logic in "shouldChangeCharactersInRange"
But i am not able to handle the index when user will remove or add any specific character from in-between.
So here are my questions :-
1. Do i need to find any library
2. There is any other default option available for it?
3. Need to write custom logic for it? If so where i can handle it correctly "shouldChangeCharactersInRange" or "textFieldDidChange"
No you dont need to find any 3rd party library for this logic
No there is no default option available for your need
Yes, you need to write a custom logic for your demand, So here it goes...
var passwordText = String()
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if textField == textFieldPassword {
var hashPassword = String()
let newChar = string.characters.first
let offsetToUpdate = passwordText.index(passwordText.startIndex, offsetBy: range.location)
if string == "" {
passwordText.remove(at: offsetToUpdate)
return true
}
else { passwordText.insert(newChar!, at: offsetToUpdate) }
for _ in passwordText.characters { hashPassword += "#" }
textField.text = hashPassword
return false
}
Swift 4:-
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if textField == textFieldPassword {
var hashPassword = String()
let newChar = string.first
let offsetToUpdate = passwordText.index(passwordText.startIndex, offsetBy: range.location)
if string == "" {
passwordText.remove(at: offsetToUpdate)
return true
}
else { passwordText.insert(newChar!, at: offsetToUpdate) }
for _ in 0..<passwordText.count { hashPassword += "#" }
textField.text = hashPassword
return false
}
return true
}
Use a normal textfield without the secure input option. When a user enters a character, save it to a string variable, and replace it in the textfield with the character you wish to present instead of the bullets.
class ViewController: UIViewController,UITextFieldDelegate {
let textField = UITextField(frame :CGRect(x:16,y:50,width:200,height: 40))
override func viewDidLoad() {
super.viewDidLoad()
textField.delegate = self
self.view.addSubview(textField)
textField.becomeFirstResponder()
}
var password: String = ""
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool{
password = password+string
textField.text = textField.text!+"#"//Character you want
print("\(password)")
return false
}
}
This is in Swift 2. Hope it Helps!!
Improved Mr. Bean's answer in swift 5. To fix Copy&Paste bugs.
var passNSString : NSString = ""
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
var hashPassword = String()
passNSString = passNSString.replacingCharacters(in: range, with: string) as NSString
for _ in 0..<passNSString.length { hashPassword += "#" }
textField.text = hashPassword
print("str", passNSString)
return false
}
Probably a simple one although no answers updated for Swift 3.
How can I append characters to a UITextField while the textfield is being edited? The characters should be appended while the user types not after the editing ends.
For example:
User types: 1
Field Updates: 1 kg
User types 123
Field Updates: 123 kg
Was trying to tackle this using EditingChanged IBActions but how can I stop the value from appending "kg" for each new character that is typed?
Example "1 kg 2 kg 3 kg"
Try this way it may help you out.
Add target for textfield on text is being editing
textField.addTarget(self, action: #selector(self.textFieldDidChange), for: .editingChanged)
In Observer method try the following way
func textFieldDidChange(textfield: UITextField) {
var text = textField.text?.replacingOccurrences(of: " KG", with: "")
text = text! + " KG"
textField.text = text
print("Text changed")
}
You want the UITextFieldDelegate method
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool
I should warn you that this is incredibly irritating to implement because of the way Swift handles Strings. Its usually better to just cast the text to NSString (which is UTF16) and deal with the range that way. However if you are just doing numbers and can live with a fixed decimal place the case is much easier to handle. Keep a custom number that represents your "real number" and just update the field to reflect your formatted number. Since you only allow digits there are finitely many cases to handle (this code will not handle copy/paste).
You must set the textfield delegate and keyboard (to numeric) in the storyboard.
class ViewController: UIViewController{
#IBOutlet weak var textField: UITextField!
fileprivate let digits: Set<String> = ["0","1","2","3","4","5","6","7","8","9"]
fileprivate let decimalPlaces = 2
fileprivate let suffix = " kg"
fileprivate lazy var formatter: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.minimumFractionDigits = self.decimalPlaces
formatter.maximumFractionDigits = self.decimalPlaces
formatter.locale = NSLocale.current
return formatter
}()
fileprivate var amount: Int = 0
override func viewDidLoad() {
super.viewDidLoad()
}
}
extension ViewController: UITextFieldDelegate {
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if digits.contains(string) {
amount *= 10
amount += Int(string)!
} else if string == "" {
amount /= 10
}
guard amount > 0 else {
textField.text = ""
return false
}
let digitsAfterDecimal = formatter.maximumFractionDigits
var value = Double(amount)
for _ in 0..<digitsAfterDecimal {
value /= 10
}
textField.text = formatter.string(from: NSNumber(value: value))! + suffix
return false
}
}
Conform the textfield delegate to the controller and try this solution.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if string == "" {
// handle backspace scenario here
return true
} else if var textString = textField.text {
let unitString = "kg"
if textString.contains(unitString) {
textString = textString.replacingOccurrences(of: unitString, with: "")
textString += string + unitString
textField.text = textString
} else {
textField.text = string + unitString
}
return false
}
return true
}
Use UITextFieldDelegate's function:
public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool // return NO to not change text
You'll have to combine the text from the textfield.text and string parameter from this function using the range parameter to get the string To Be formed on textfield while you type/paste/clear based on range.
Keep track on range.length as it always gives the count of string being deleted/cleared and is 0 when you enter text. And range.location gives the position of edit.
Then you can set the formed string to textfield.text and return false Remember - return false and not true
This code allows the users to type in certain characters only:
let allowedChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-.";
func textField(
textField: UITextField,
shouldChangeCharactersInRange range: NSRange,
replacementString string: String)
-> Bool
{
let set = NSCharacterSet(charactersInString: allowedChars);
let filtered = string
.componentsSeparatedByCharactersInSet(set)
.joinWithSeparator("");
return filtered != string;
}
How can i make it so that it allows a just one textfield called "txtField1"?
You can check whether your textField is the one currently selected in your shouldChangeCharactersInRange method because as you can see in the parameters of this method you have access to the property textField which correspond to the current textField that you are selecting, so just check this at the beginning :
if texField == txtField1 {
// Do your stuff
} else {
// Do something else
}
Assign a value to the tag on your textfield that is unique to it (for example, 1).
txtField1.tag = 1
Then, update your method like this:
func textField(
textField: UITextField,
shouldChangeCharactersInRange range: NSRange,
replacementString string: String)
-> Bool
{
if textField.tag == 1 {
let set = NSCharacterSet(charactersInString: allowedChars)
let filtered = string
.componentsSeparatedByCharactersInSet(set)
.joinWithSeparator("")
return filtered != string
}
return true
}
You can also omit the ; in Swift.
How can I get limit the user's TextField input to numbers in Swift?
You can use UITextFieldDelegate’s shouldChangeCharactersInRange method to limit the user's input to numbers:
func textField(textField: UITextField,
shouldChangeCharactersInRange range: NSRange,
replacementString string: String) -> Bool {
// Create an `NSCharacterSet` set which includes everything *but* the digits
let inverseSet = NSCharacterSet(charactersInString:"0123456789").invertedSet
// At every character in this "inverseSet" contained in the string,
// split the string up into components which exclude the characters
// in this inverse set
let components = string.componentsSeparatedByCharactersInSet(inverseSet)
// Rejoin these components
let filtered = components.joinWithSeparator("") // use join("", components) if you are using Swift 1.2
// If the original string is equal to the filtered string, i.e. if no
// inverse characters were present to be eliminated, the input is valid
// and the statement returns true; else it returns false
return string == filtered
}
Updated for Swift 3:
func textField(_ textField: UITextField,
shouldChangeCharactersIn range: NSRange,
replacementString string: String) -> Bool {
// Create an `NSCharacterSet` set which includes everything *but* the digits
let inverseSet = NSCharacterSet(charactersIn:"0123456789").inverted
// At every character in this "inverseSet" contained in the string,
// split the string up into components which exclude the characters
// in this inverse set
let components = string.components(separatedBy: inverseSet)
// Rejoin these components
let filtered = components.joined(separator: "") // use join("", components) if you are using Swift 1.2
// If the original string is equal to the filtered string, i.e. if no
// inverse characters were present to be eliminated, the input is valid
// and the statement returns true; else it returns false
return string == filtered
}
For anyone looking for a shorter answer, I've found this quite useful.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
// remove non-numerics and compare with original string
return string == string.filter("0123456789".contains)
}
Works in XCode 10.1, Swift 4.2
In swift 4.1 and Xcode 10
Add UITextFieldDelegate to your class
class YourViewController: UIViewController, UITextFieldDelegate
Then write this code in your viewDidLoad()
yourTF.delegate = self
Write this textfield delegate function
//MARK - UITextField Delegates
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
//For numers
if textField == yourTF {
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
}
1st you have to inherit the UITextViewDelegate class with you own
class
class ViewController: UIViewController, UITextViewDelegate {
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)
let Range = range.length + range.location > (fnameTF.text?.count)!
if Range == false && alphabet == false {
return false
}
let NewLength = (fnameTF.text?.count)! + string.count - range.length
return NewLength <= 10
} else {
return false
}
}
Well the iOS provides no such functionality where you can specify textfield to accept only numeric characters. The only way through would be, one of UITextFieldDelegate methods, which is as follows,
(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
You need to implement the following method and intercept the entered character and either through the following regular expression
"^([0-9]+)?(\\.([0-9]{1,2})?)?$"
or
[NSCharacterSet decimalDigitCharacterSet]
you can find out whether the entered character is numeric and return YES if it matches the regular expression or character set else return NO.