How shouldChangeCharactersInRange works in Swift?

IosObjective CSwiftUitextfielddelegateNsrange

Ios 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
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionRyanView Question on Stackoverflow
Solution 1 - IosVyacheslavView Answer on Stackoverflow
Solution 2 - IosMike PollardView Answer on Stackoverflow
Solution 3 - IosMobile DanView Answer on Stackoverflow
Solution 4 - IosAndrey GordeevView Answer on Stackoverflow
Solution 5 - IosyoAlex5View Answer on Stackoverflow
Solution 6 - IosHafiz AbdullahView Answer on Stackoverflow
Solution 7 - IosblackjacxView Answer on Stackoverflow
Solution 8 - IosclearlightView Answer on Stackoverflow
Solution 9 - IosPavle MijatovicView Answer on Stackoverflow