How shouldChangeCharactersInRange works in Swift?
Asked Answered
S

10

96

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?

Sharp answered 2/9, 2014 at 10:43 Comment(0)
C
216

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.

Claudette answered 2/2, 2018 at 15:8 Comment(5)
It's appending one extra character while typing, what is the cause of it?.Chimkent
@SagarDaundkar post your example textClaudette
@PrashantTukadiya I said about itClaudette
@Claudette sorry Any work around for this my question #50266986Amadeo
@PrashantTukadiya I solved it using a some variable which stores a "typing" stateClaudette
M
82

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
}
Moitoso answered 2/9, 2014 at 11:2 Comment(6)
That's curious that we have to convert to NSString while using with Swift string it doesn't work due to different argument types.Insurrection
any way without using the old NSString?Peak
@Peak I don't think there's one that isn't arduously painful in comparison. NSString <> String conversions are far from the worst though, given they are toll-free bridgedIasis
It does not take more than 2 characters at a timeKioto
Not work in few language such as Korean. Korean word is not completed with just appending a last character. In case of typing "대한민국", txtAfterUpdate would be "대한민구ㄱ". FYI for Koreans.Lout
Does not work when a password protected field is about to be cleared with a single backspaceBook
E
44

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.

Eustazio answered 6/4, 2016 at 23:19 Comment(2)
Make sure textFieldText is NSString, else it would throw an error with range. Took me few mins to solve. Cheers.Perimeter
Why though? Why can we not use the normal Swift String class?Spermatozoid
V
13

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
}
Vanthe answered 20/8, 2016 at 11:12 Comment(0)
G
3

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
}
Gallicanism answered 23/8, 2021 at 13:7 Comment(1)
This seems to be copied entirely from hackingwithswift.com/example-code/uikit/…Pluviometer
G
2

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
Goat answered 11/10, 2020 at 19:0 Comment(0)
B
1

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.

Borax answered 7/5, 2022 at 16:39 Comment(0)
S
0

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

Sapajou answered 11/4, 2017 at 14:53 Comment(2)
wouldn't this overwrite existing characters instead of inserting them? i still need to try this out, but afaik the shouldChangeCharactersIn method is called before the textField content is changedEscuage
Yes that is true, but you already can construct the new string that is set to the textfield when you return true in this function. Basically you can do what you want with the current and the new text field content plus you can control if you want to reject the change plus plus you can modify the string that is set. So you have full control here.Sapajou
I
-1

Swift 13

func textFieldDidChangeSelection(_ textField: UITextField) {
    DispatchQueue.main.async { 
        self.textBinding.wrappedValue = textField.text ?? ""
    }
}
Invigilate answered 9/3, 2023 at 14:13 Comment(2)
As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.Washbowl
DispatchQueue.main.async is a pain for new <-> intermediate Swift coders... this answer won't be helpful unless you explain why this solves the problem.Fleck
W
-7

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
}
Westward answered 3/11, 2016 at 9:56 Comment(2)
what if the user is deleting one character? You must use the range.Ales
What if they edited characters int he middle of the string? Not using the range is wrong.Aniakudo

© 2022 - 2024 — McMap. All rights reserved.