I'm getting the following error in Crashlytics
LoginViewController.swift line 277
LoginViewController.textField(UITextField,
shouldChangeCharactersInRange : _NSRange, replacementString : String)
-> Bool
There's the code in my project:
// MARK: - UITextFieldDelegate
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
if textField == txtSearch && string != "\n"{
var substring: String = textField.text!
substring = (substring as NSString).stringByReplacingCharactersInRange(range, withString: string)
self.searchAutocompleteEntriesWithSubstring(substring)
}
return true;
}
This error ocurrs only sometimes
You are force unwrapping your textField.text! which may be nil. Try below code:
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
if textField == txtSearch && string != "\n"{
if let text = textField.text {
let substring = (text as NSString).stringByReplacingCharactersInRange(range, withString: string)
self.searchAutocompleteEntriesWithSubstring(substring)
}
}
return true
}
Related
I am using phone number texfield, now i am using this format for texfield (#) ### ### ###, now issue is that i want first character 0 as compulsary, like this (0) 959 554 545, so user enter whatever first character must be typed 0,
this is my code
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let newString = textField.text
if ((newString == "") && (newString?.count == 0)){
txtMobileNumber.text = "0"
return true
}else if ((newString?.count)! > 0){
return true
}
return false
}
In shouldChangeCharactersIn method return false if new string count is greater than 15. Else remove (0) and empty spaces, then if the string isn't empty add (0) at the beginning. Then split the string by every 3 characters and join all string with space separator.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
var oldText = (textField.text! as NSString).replacingCharacters(in: range, with: string)
if oldText.count > 15 { return false }
oldText = oldText.replacingOccurrences(of: "(0)", with: "").replacingOccurrences(of: " ", with: "")
if !oldText.isEmpty {
oldText = "(0)" + oldText
}
let newText = String(stride(from: 0, to: oldText.count, by: 3).map {
let sIndex = String.Index(encodedOffset: $0)
let eIndex = oldText.index(sIndex, offsetBy: 3, limitedBy: oldText.endIndex) ?? oldText.endIndex
return String(oldText[sIndex..<eIndex])
}.joined(separator: " "))
textField.text = newText
return false
}
In shouldChangeCharactersIn method of UITextFieldDelegate,
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if let text = textField.text {
let str = (text as NSString).replacingCharacters(in: range, with: string).replacingOccurrences(of: "(0)", with: "")
if !str.isEmpty {
textField.text = "(0)" + str
} else {
textField.text = nil
}
}
return false
}
Append (0) to the newly created string, everytime the textField is edited.
If you create Enum, you can choice your textField type and make the extension properties for this field
func textField(_ textField: UITextField,
shouldChangeCharactersIn range: NSRange,
replacementString string: String) -> Bool {
if cellType == .phone {
guard var sanitizedText = textField.text else { return true }
if !sanitizedText.isEmpty && !sanitizedText.hasPrefix("(0)") {
sanitizedText.text = "(0)" + sanitizedText
}
}
}
I am looking to cap the amount of characters a user can type into a textfield at 14. Here is the code that I have found documentation on.
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
let currentCharacterCount = userNameTextField.text?.characters.count ?? 0
if (range.length + range.location > currentCharacterCount){
return false
}
let newLength = currentCharacterCount + string.characters.count - range.length
return newLength <= 14
}
but I do not feel that I am implementing this correctly. I have set
userNameTextField.delegate = self
in the viewDidLoad, and I am conforming to the UITextFieldDelegate protocol.
You state you are using Swift 3. The signature of many methods changed in Swift 3. You need to use:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
}
Not the old signature posted in your question.
If it's still not being called, then you never set the text field's delegate property.
Try this instead:
func textField(textField: UITextField!, shouldChangeCharactersInRange range: NSRange, replacementString string: String!) -> Bool {
let currentString: NSString = (textField.text ?? "") as NSString
let newString = currentString.replacingCharacters(in: range, with: string)
return newString.characters.count <= 14
}
Try this for swift 3:
let limit=4;
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
guard let text = txtSMSCode.text else { return true }
let newLength = text.characters.count + string.characters.count - range.length
return newLength <= limit
}
I want to restrict the use of the spacebar in the beginning of the textfield in iOS. I tried to use the below logic but it is not allowing spaces anywhere between the words. Please help me in this case.
if self.rangeOfCharacterFromSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()) != nil {
return false
}
return true
For TextField
Swift 5.2, Xcode 11.4
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
guard range.location == 0 else {
return true
}
let newString = (textField.text! as NSString).replacingCharacters(in: range, with: string) as NSString
return newString.rangeOfCharacter(from: CharacterSet.whitespacesAndNewlines).location != 0
}
If you need to do what you described, you can use the textView(textView: UITextView, shouldChangeTextInRange range: NSRange, replacementText text: String) method in UITextViewDelegate.
Example:
func textView(textView: UITextView, shouldChangeTextInRange range: NSRange, replacementText text: String) -> Bool {
// as #nhgrif suggested, we can skip the string manipulations if
// the beginning of the textView.text is not touched.
guard range.location == 0 else {
return true
}
let newString = (textView.text as NSString).stringByReplacingCharactersInRange(range, withString: text) as NSString
return newString.rangeOfCharacterFromSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()).location != 0
}
First, we construct the new string that will be shown in the textView.
And then we check if it start with a whitespace, tab or newline character.
If so, we return false so the the textView won't place the new text in.
Otherwise, put the new text into the textView.
Note: We need to check the whole string instead of checking the replacementText to deal with copy-paste actions.
Another possible way is not restricting the text the user typed, but trimming the result text when you need to use the value.
let myText = textView.text.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet())
Edit: add a guard clause to make the method more performant based on #nhgrif's comment.
Write this in textView delegate:
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
guard range.location == 0 else {
return true
}
let newString = (textView.text as NSString).replacingCharacters(in: range, with: text) as NSString
return newString.rangeOfCharacter(from: CharacterSet.whitespacesAndNewlines).location != 0
}
public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if (range.location == 0 && string == " ") {
return false
}
}
Swift 4.1
Just simple with Single line of code you can stop whitespace
For All UITextView
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
textView.text = textView.text.replacingOccurrences(of: " ", with: "")
}
For Single UITextView
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if textView == textViewArea
{
textView.text = textView.text.replacingOccurrences(of: " ", with: "")
}
return true
}
Here improvement in Answer You can disable blankSpaces until your First Character Enter
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
let whitespaceSet = NSCharacterSet.whitespaces
let range = text.rangeOfCharacter(from: whitespaceSet)
if let _ = range {
let txt = textView.text ?? ""
if txt.isBlankByTrimming || txt.isEmpty {
return false
}else {
return true
}
}
else {
return true
}
}
After this before saving your final string you Can trim Spaces and newline from End Of the string .
Example : "MyName Surname "
extension String {
var isBlankByTrimming: Bool {
let trimmed = self.trimmingCharacters(in:
CharacterSet.whitespacesAndNewlines)
return trimmed.isEmpty
}
func trimWhitespacesAndNewlines() -> String{
return self.trimmingCharacters(in:
CharacterSet.whitespacesAndNewlines)
}}
for use ::
let yourString = "MyName Surname "
let finalString = yourString.trimWhitespacesAndNewlines()
//User never input space in text field(swift 5)...
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if string == " " {
// If consecutive spaces entered by user
return false
}
}
Getting this error when checking the range for string characters...
#objc func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
let shouldChange = false
let text = textField.text
var newString = text!.stringByReplacingCharactersInRange(range, withString: string) as? NSString
if newString.length > 14{
newString = newString.substringToIndex(14)
}
textField.text = newString.uppercaseString
return shouldChange
}
Instead of text! say (text! as NSString).
var newString = (text! as NSString).stringByReplacingCharactersInRange(range, withString: string) as? NSString
Swift 4
func textField(_ textField: UITextField,
shouldChangeCharactersIn range: NSRange,
replacementString string: String) -> Bool {
if let oldString = textField.text {
let newString = oldString.replacingCharacters(in: Range(range, in: oldString)!,
with: string)
// ...
}
// ...
}
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?
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.
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
}
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.
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
}
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
}
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
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
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.
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
}