How shouldChangeCharactersInRange works in Swift?
IosObjective CSwiftUitextfielddelegateNsrangeIos Problem Overview
I'm using shouldChangeCharactersInRange as a way of using on-the-fly type search.
However I'm having a problem, shouldChangeCharactersInRange gets called before the text field actually updates:
In Objective C, I solved this using using below:
-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
NSString * searchStr = [textField.text stringByReplacingCharactersInRange:range withString:string];
return YES;
}
However, I've tried writing this in Swift:
func textField(textField: UITextField!, shouldChangeCharactersInRange range: NSRange, replacementString string: String!) -> Bool {
let txtAfterUpdate:NSString = self.projectSearchTxtFld.text as NSString
txtAfterUpdate.stringByReplacingCharactersInRange(range, withString: string)
self.callMyMethod(txtAfterUpdate)
return true
}
The method still gets called before I get a value?
Ios Solutions
Solution 1 - Ios
Swift 4, Swift 5
This method doesn't use NSString
// MARK: - UITextFieldDelegate
extension MyViewController: UITextFieldDelegate {
func textField(_ textField: UITextField,
shouldChangeCharactersIn range: NSRange,
replacementString string: String) -> Bool {
if let text = textField.text,
let textRange = Range(range, in: text) {
let updatedText = text.replacingCharacters(in: textRange,
with: string)
myvalidator(text: updatedText)
}
return true
}
}
Note. Be careful when you use a secured text field.
Solution 2 - Ios
stringByReplacingCharactersInRange
return a new string, so how about:
func textField(textField: UITextField!, shouldChangeCharactersInRange range: NSRange, replacementString string: String!) -> Bool {
if let text = textField.text as NSString? {
let txtAfterUpdate = text.replacingCharacters(in: range, with: string)
self.callMyMethod(txtAfterUpdate)
}
return true
}
Solution 3 - Ios
Swift 3 & 4
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let textFieldText: NSString = (textField.text ?? "") as NSString
let txtAfterUpdate = textFieldText.replacingCharacters(in: range, with: string)
callMyMethod(txtAfterUpdate)
return true
}
func textFieldShouldClear(_ textField: UITextField) -> Bool {
callMyMethod("")
return true
}
Swift 2.2
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
let textFieldText: NSString = textField.text ?? ""
let txtAfterUpdate = textFieldText.stringByReplacingCharactersInRange(range, withString: string)
callMyMethod(txtAfterUpdate)
return true
}
func textFieldShouldClear(textField: UITextField) -> Bool {
callMyMethod("")
return true
}
Though the textField.text
property is an optional, it cannot be set to nil. Setting it to nil is changed to empty string within UITextField
. In the code above, that is why textFieldText
is set to empty string if textField.text
is nil (via the nil coalescing operator ??
).
Implementing textFieldShouldClear(_:)
handles the case where the text field's clear button is visible and tapped.
Solution 4 - Ios
In Swift 3 it would look like this:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let text: NSString = (textField.text ?? "") as NSString
let resultString = text.replacingCharacters(in: range, with: string)
return true
}
Solution 5 - Ios
shouldChangeCharactersInRange
func textField(textField: UITextField!, shouldChangeCharactersInRange range: NSRange, replacementString string: String!) -> Bool { }
This function is called when changes are made but UI is not updated and waiting for your choice
Take a look at returned bool value
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool
- If you return
true
- it means that iOS accept changes(text, caret...) - If you return
false
- it means that you are responsible for all this stuff
Solution 6 - Ios
shouldChangeCharactersIn
is called on every key press.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
// get the current text, or use an empty string if that failed
let currentText = textField.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 16 characters
return updatedText.count <= 16
}
Solution 7 - Ios
Swift 3
If you want to pre-process the characters the user typed or pasted, the following solution workes like a charm
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let strippedString = <change replacements string so it fits your requirement - strip, trim, etc>
// replace current content with stripped content
if let replaceStart = textField.position(from: textField.beginningOfDocument, offset: range.location),
let replaceEnd = textField.position(from: replaceStart, offset: range.length),
let textRange = textField.textRange(from: replaceStart, to: replaceEnd) {
textField.replace(textRange, withText: strippedString)
}
return false
}
Find it here: https://gist.github.com/Blackjacx/2198d86442ec9b9b05c0801f4e392047
Solution 8 - Ios
This is essentially @Vyacheslav's answer independently arrived at for my own use case, just in case the stylistic approach resonates :-)
func textField(_ textField: UITextField, shouldChangeCharactersIn nsRange: NSRange, replacementString: String) -> Bool {
let range = Range(nsRange, in: textField.text!)!
let textWouldBecome = textField.text!.replacingCharacters(in: range, with: replacementString)
if textWouldBecome != eventModel.title {
self.navigationItem.setHidesBackButton(true, animated: true)
} else {
self.navigationItem.setHidesBackButton(false, animated: true)
}
return true
}
Replace eventModel.title with whatever you're checking for the change against obviously.
Solution 9 - Ios
To get the exact text in the my UITextField component in Swift 3.0 I used:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let enteredTxt = textField.text! + string
doSomethingWithTxt(enteredTxt) //some custom method
}