598

How can I detect any text changes in a textField? The delegate method shouldChangeCharactersInRange works for something, but it did not fulfill my need exactly. Since until it returns YES, the textField texts are not available to other observer methods.

e.g. in my code calculateAndUpdateTextFields did not get the updated text, the user has typed.

Is their any way to get something like textChanged Java event handler.

- (BOOL)textField:(UITextField *)textField 
            shouldChangeCharactersInRange:(NSRange)range 
            replacementString:(NSString *)string 
{
    if (textField.tag == kTextFieldTagSubtotal 
        || textField.tag == kTextFieldTagSubtotalDecimal
        || textField.tag == kTextFieldTagShipping
        || textField.tag == kTextFieldTagShippingDecimal) 
    {
        [self calculateAndUpdateTextFields];

    }

    return YES;
}
Juan Boero
  • 5,451
  • 37
  • 56
karim
  • 14,810
  • 7
  • 55
  • 92

22 Answers22

1111

From proper way to do uitextfield text change call back:

I catch the characters sent to a UITextField control something like this:

// Add a "textFieldDidChange" notification method to the text field control.

In Objective-C:

[textField addTarget:self 
              action:@selector(textFieldDidChange:) 
    forControlEvents:UIControlEventEditingChanged];

In Swift:

textField.addTarget(self, action: #selector(textFieldDidChange), for: .editingChanged)

Then in the textFieldDidChange method you can examine the contents of the textField, and reload your table view as needed.

You could use that and put calculateAndUpdateTextFields as your selector.

jtbandes
  • 104,858
  • 34
  • 217
  • 242
Daniel G. Wilson
  • 14,499
  • 2
  • 28
  • 39
  • 19
    This is the better solution - because you can also set this in Nibs or Storyboards, and you don't have to write out excessive UITextFieldTextDidChangeNotification code – PostCodeism Feb 27 '13 at 17:57
  • 3
    The only issue is. It doesn't work for me together with - (BOOL) textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string – iWheelBuy Sep 27 '13 at 05:21
  • 6
    @iWheelBuy Only when it returns `NO`, which is logical, because when you return `NO` from this method, you're basically saying the text in the field shouldn't change. – gitaarik Mar 19 '14 at 14:39
  • 2
    This is great for editing caused changes. It doesn't catch programmatic changes that occur via: `textField.text = "Some new value";`. Is there a clever way to catch this? – Benjohn Jan 16 '15 at 09:54
  • 1
    If you use the `UITextInput` protocol methods to change the text the action gets triggered. So just don't do `textField.text = newText` :) – DeFrenZ Apr 30 '15 at 17:24
  • what's the signature of the textFieldDidChange method? – user230910 May 27 '15 at 06:50
  • 1
    This works, but the callback seems to fire before the change is reflected in the UI. My test for text length == 4 is fired while the text field is still displaying 3 characters. Maybe there is a better answer for those who want the event to fire AFTER the text field changes – Chicowitz Jan 03 '16 at 23:56
  • @Chicowitz you could use textFieldDidEndEditing? – Supertecnoboff Jun 01 '16 at 14:43
  • I was implementing pin entry, where the logic is supposed to execute after 4 characters are entered. I ended up using textFieldDidChange above and inserting a small delay so that the change was reflected in the UI first. – Chicowitz Jun 01 '16 at 15:08
  • 1
    Sadly this and the `UITextFieldTextDidChangeNotification` approach does not work anymore for me in Swift 2.3 and Xcode 8 Beta 6. I'm not sure if it is a bug or sth else. Has anybody seen the same behaviour? – blackjacx Aug 20 '16 at 11:54
  • Say the user is entering 20 characters. Would I get notified at the beginning of the text entering or at its end? I want to get informed at its end, something like a completion handler of text entered. Though not sure if that's possible and then send that text to another viewController. I fully know how to do it using delegates, but I'm trying to learn how to do it using closures – Honey Dec 24 '16 at 21:50
  • 14
    You would have thought with Apple's `UITextFieldDelegate` that something like `func textField: UITextField, didChangeText text: String` would have been included, but... *(gives guys at Apple a dirty look)* – Brandon A Apr 06 '17 at 02:25
  • That function is available in iOS 13 under the name `textFieldDidChangeSelection`. So you can put the following code inside an `if` checking for `#available(iOS 13.0, *)`. `.addTarget(self, action: #selector(textFieldDidChangeSelection(_:)), for: .editingChanged)`. And of course you would have implemented that function available from the iOS 13 textField delegate. – Rémi Belzanti Oct 15 '19 at 14:46
  • This is simply wrong. It's a basic nuisance in iOS that controls DO NOT emit that event in the case of *programmatic* changes. – Fattie Jan 20 '20 at 18:16
  • any idea why textFieldDidChange method didn't get invoke if we change value of textfield programmatically ? other hand it get invoke only if we edit through typing – Krishna Sharma Jul 21 '20 at 17:47
404

XenElement's answer is spot on.

The above can be done in interface builder too by right-clicking on the UITextField and dragging the "Editing Changed" send event to your subclass unit.

UITextField Change Event

William T.
  • 12,040
  • 3
  • 52
  • 51
  • 2
    Easy it may be, but after 10-15 minutes of searching, I hadn't discovered this possibility until stumbling upon your answer by chance. Have another +1 from me; it's nice to have both the code-based and IB-based solutions highly voted on questions like this. – Mark Amery Jul 25 '13 at 12:03
  • I just checked it in 6.3 and it's there. This is a UITextView and right click on it and see "Editing Changed". – William T. Mar 12 '15 at 15:35
  • 6
    why on earth is it named Editing Changed?? mad! thanks for the solution though :-) – malhal Apr 20 '15 at 14:43
  • 3
    Because the event is called when editing changes, whereas Value Changed happens when the textfield finishes editing i.e. resigns first responder. UITextFieldTextDidChangeNotification is a UITextField notification, while UIControlEventValueChanged is implemented by UIControl which UIButton subclasses. – Camsoft Sep 17 '15 at 09:31
  • 2
    I notice this doesn't work as user tabs out of the textfield and an autocorrect text replaces the content – noobular Aug 19 '16 at 22:19
  • For Obj C if you are going to connect outlet from NIB the method should contain IBAction as follow; - (IBAction)textFieldDidChange:(UITextField *)textField {} – Engnyl Jul 11 '18 at 11:16
  • Does this require **subclassing** the UITextField? Can't assign that action to the parent view controller via Xcode? – pkamb Feb 05 '20 at 19:57
139

to set the event listener:

[self.textField addTarget:self action:@selector(textFieldDidChange:) forControlEvents:UIControlEventEditingChanged];

to actually listen:

- (void)textFieldDidChange:(UITextField *)textField {
    NSLog(@"text changed: %@", textField.text);
}
pkamb
  • 26,648
  • 20
  • 124
  • 157
asdf
  • 1,447
  • 1
  • 9
  • 11
  • cool. I like this approach, because I'd like to implement textField-change-listening in a ViewController base class which is already TextFieldDelegate. This way I can addTarget:baseControllerMethod for (textField in subviews) like a charm. THX! – HBublitz Aug 28 '14 at 19:20
94

Swift:

yourTextfield.addTarget(self, action: #selector(textFieldDidChange(textField:)), for: .editingChanged)

Then, implement the callback function:

@objc final private func textFieldDidChange(textField: UITextField){

print("Text changed")

}
Juan Boero
  • 5,451
  • 37
  • 56
  • 4
    Worked for me, I had already tried this but didn't realize the function needed a textField argument. If this is not working for you, make sure to check that your selector has an argument for the textField to be passed in (even if you don't use it). – werm098 Jun 14 '16 at 13:45
  • Add _ before textField in textFieldDidChange like this: func textFieldDidChange(textField: UITextField) { ... } – Makalele Jan 02 '17 at 23:32
31

As stated here: UITextField text change event, it seems that as of iOS 6 (iOS 6.0 and 6.1 checked) it is not possible to fully detect changes in UITextField objects just by observing the UITextFieldTextDidChangeNotification.

It seems that only those changes made directly by the built-in iOS keyboard are tracked now. This means that if you change your UITextField object just by invoking something like this: myUITextField.text = @"any_text", you won't be notified about any changes at all.

I don't know if this is a bug or it is intended. Seems like a bug since I haven't found any reasonable explanation in documentation. This is also stated here: UITextField text change event.

My "solution" to this is to actually post a notification by myself for every change I make to my UITextField (if that change is done without using the built-in iOS keyboard). Something like this:

myUITextField.text = @"I'm_updating_my_UITextField_directly_in_code";

NSNotification *myTextFieldUpdateNotification  = 
  [NSNotification notificationWithName:UITextFieldTextDidChangeNotification
                  object:myUITextField];

[NSNotificationCenter.defaultCenter 
  postNotification:myTextFieldUpdateNotification];

This way you are 100% confident that you'll receive the same notification when you change the .text property of your UITextField object, either when you update it "manually" in your code or through the built-in iOS keyboard.

It is important to consider that, since this is not a documented behavior, this approach may lead to 2 notifications received for the same change in your UITextField object. Depending on your needs (what you actually do when your UITextField.text changes) this could be an inconvenience for you.

A slightly different approach would be to post a custom notification (this is, with a custom name other than UITextFieldTextDidChangeNotification) if you actually need to know whether the notification was yours or "iOS-made".

EDIT:

I've just found a different approach which I think could be better:

This involves the Key-Value Observing (KVO) feature of Objective-C (http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/KeyValueObserving/KeyValueObserving.html#//apple_ref/doc/uid/10000177-BCICJDHA).

Basically, you register yourself as an observer of a property and if this property changes you get notified about it. The "principle" is quite similar to how NSNotificationCenter works, being the main advantage that this approach works automatically also as of iOS 6 (without any special tweak like having to manually post notifications).

For our UITextField-scenario this works just fine if you add this code to, for example, your UIViewController that contains the text field:

static void *myContext = &myContext;

- (void)viewDidLoad {
  [super viewDidLoad];

  //Observing changes to myUITextField.text:
  [myUITextField addObserver:self forKeyPath:@"text"
    options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld 
    context:myContext];

}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object 
change:(NSDictionary *)change context:(void *)context {

  if(context == myContext) {
    //Here you get notified every time myUITextField's "text" property is updated
    NSLog(@"New value: %@ - Old value: %@",
      [change objectForKey:NSKeyValueChangeNewKey],
      [change objectForKey:NSKeyValueChangeOldKey]);
  }
  else 
    [super observeValueForKeyPath:keyPath ofObject:object 
      change:change context:context];

}

Credit to this answer regarding "context" management: https://stackoverflow.com/a/12097161/2078512

Note: Seems like while you are in the process of editing a UITextField with the built-in iOS keyboard, the "text" property of the text field is not updated with every new letter typed/removed. Instead, the text field object gets updated "as a whole" after you resign the first responder status of the text field.

Community
  • 1
  • 1
ercolemtar
  • 375
  • 4
  • 6
  • 6
    Why put yourself through this, just use the target-action method from XenElement's answer. If you need tens of lines of code do do something that "ought" to be simple, you're probably doing it wrong. – jrturton Feb 22 '13 at 11:10
  • 6
    That would seem to be intended behaviour. No other delegate methods (e.g. scrolling, selection) get called for events originated by code. – jrturton Feb 22 '13 at 16:45
  • 1
    Note that [UIKit is not guaranteed to be KVO compliant](http://stackoverflow.com/a/6352525/1402846), so the KVO solution won’t necessarily work. – Pang Sep 08 '13 at 09:42
  • @jrturton , regarding your "why" question, it's completely commonplace that in some other class (perhaps a decoration, animation or the like) you need to respond to what is happening in the text field. – Fattie Jan 20 '20 at 18:17
28

We can easily configure that from Storyboard, CTRL drag the @IBAction and change event as following:

enter image description here

pedrouan
  • 11,915
  • 2
  • 55
  • 71
bikram sapkota
  • 946
  • 1
  • 11
  • 17
15

Here in swift version for same.

textField.addTarget(self, action: "textFieldDidChange:", forControlEvents: UIControlEvents.EditingChanged)

func textFieldDidChange(textField: UITextField) {

}

Thanks

Hindu
  • 2,824
  • 21
  • 40
12

I resolved the issue changing the behavior of shouldChangeChractersInRange. If you return NO the changes won't be applied by iOS internally, instead you have the opportunity to change it manually and perform any actions after the changes.

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
    //Replace the string manually in the textbox
    textField.text = [textField.text stringByReplacingCharactersInRange:range withString:string];
    //perform any logic here now that you are sure the textbox text has changed
    [self didChangeTextInTextField:textField];
    return NO; //this make iOS not to perform any action
}
Pauls
  • 2,419
  • 17
  • 19
  • 3
    The problem with this solution is: The Cursor position gets set to the END of the field (as soon as you do textField.text = ...) . So if the user wants to edit characters in the middle of the field, then with each keystroke, their cursor will go to the end of the field. Try it and see. – FranticRock Aug 27 '15 at 14:20
  • Good point @Alex , but simple to work around. Just calculate the resulting value in a local NSString and pass that to your didChangeTextInTextField:toText: method, then return YES to let iOS perform the update. – jk7 Sep 17 '16 at 23:27
11

Swift Version tested:

//Somewhere in your UIViewController, like viewDidLoad(){ ... }
self.textField.addTarget(
        self, 
        action: #selector(SearchViewController.textFieldDidChange(_:)),
        forControlEvents: UIControlEvents.EditingChanged
)

Parameters explained:

self.textField //-> A UITextField defined somewhere in your UIViewController
self //-> UIViewController
.textFieldDidChange(_:) //-> Can be named anyway you like, as long as it is defined in your UIViewController

Then add the method you created above in your UIViewController:

//Gets called everytime the text changes in the textfield.
func textFieldDidChange(textField: UITextField){

    print("Text changed: " + textField.text!)

}
kemicofa ghost
  • 14,587
  • 5
  • 63
  • 112
7

Swift 4

func addNotificationObservers() {

    NotificationCenter.default.addObserver(self, selector: #selector(textFieldDidChangeAction(_:)), name: .UITextFieldTextDidChange, object: nil)

}

@objc func textFieldDidChangeAction(_ notification: NSNotification) {

    let textField = notification.object as! UITextField
    print(textField.text!)

}
acidgate
  • 1,720
  • 1
  • 11
  • 25
5

For Swift 3.0:

let textField = UITextField()

textField.addTarget(
    nil,
    action: #selector(MyClass.textChanged(_:)),
    for: UIControlEvents.editingChanged
)

using class like:

class MyClass {
    func textChanged(sender: Any!) {

    }
}
pedrouan
  • 11,915
  • 2
  • 55
  • 71
4

Swift 3 Version

yourTextField.addTarget(self, action: #selector(YourControllerName.textChanges(_:)), for: UIControlEvents.editingChanged)

And get the changes in here

func textChanges(_ textField: UITextField) {
    let text = textField.text! // your desired text here
    // Now do whatever you want.
}

Hope it helps.

Riajur Rahman
  • 1,794
  • 17
  • 25
3

Swift 3.1:

Selector: ClassName.MethodName

  cell.lblItem.addTarget(self, action: #selector(NewListScreen.fieldChanged(textfieldChange:)), for: .editingChanged)

  func fieldChanged(textfieldChange: UITextField){

    }
Ucdemir
  • 2,070
  • 2
  • 21
  • 35
2

You should use the notification to solve this problem,because the other method will listen to the input box not the actually input,especially when you use the Chinese input method. In viewDidload

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textFiledEditChanged:)
                                                 name:@"UITextFieldTextDidChangeNotification"
                                               object:youTarget];

then

- (void)textFiledEditChanged:(NSNotification *)obj {
UITextField *textField = (UITextField *)obj.object;
NSString *toBestring = textField.text;
NSArray *currentar = [UITextInputMode activeInputModes];
UITextInputMode *currentMode = [currentar firstObject];
if ([currentMode.primaryLanguage isEqualToString:@"zh-Hans"]) {
    UITextRange *selectedRange = [textField markedTextRange];
    UITextPosition *position = [textField positionFromPosition:selectedRange.start offset:0];
    if (!position) {
        if (toBestring.length > kMaxLength)
            textField.text =  toBestring;
} 

}

finally,you run,will done.

HsuChihYung
  • 109
  • 1
  • 1
  • 6
2

Swift 3 Version:

class SomeClass: UIViewController, UITextFieldDelegate { 

   @IBOutlet weak var aTextField: UITextField!


    override func viewDidLoad() {
        super.viewDidLoad()

        aTextField.delegate = self
        aTextField.addTarget(self, action: #selector(SignUpVC.textFieldDidChange), for: UIControlEvents.editingChanged)        
    }

   func textFieldDidChange(_ textField: UITextField) {

       //TEXT FIELD CHANGED..SECRET STUFF

   }

}

Don't forget to set the delegate.

Joseph Francis
  • 825
  • 1
  • 13
  • 20
  • I have 6 text field in my view controller and I just want to check if the last text field has been changed print("last Text Field has been Changed!") can you help? – Saeed Rahmatolahi Jun 18 '17 at 11:02
  • First make Iboutlet for your textField. Then set the delegate and addTarget like I showed in my answer. In delegate function textFieldDidChange, add this: "if textField == yourLastTextField { print("last Text Field has been Changed!") } Right now I don't have access to my computer. I'll make this comment more readable when I get to my computer. – Joseph Francis Jun 18 '17 at 11:08
2

With closure:

   class TextFieldWithClosure: UITextField {
    var targetAction: (() -> Void)? {
        didSet {
            self.addTarget(self, action: #selector(self.targetSelector), for: .editingChanged)
        }
    }

    func targetSelector() {
        self.targetAction?()
    }
    }

and using

textField.targetAction? = {
 // will fire on text changed
 }
ebohdas
  • 21
  • 1
2
  1. KVO solutions do not work

KVO does NOT work in iOS for controls: http://stackoverflow.com/a/6352525/1402846 https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/KVO.html

  1. In the observing class, you do this:

Given that you know the text view you want to watch:

var watchedTextView: UITextView!

Do this:

NotificationCenter.default.addObserver(
    self,
    selector: #selector(changed),
    name: UITextView.textDidChangeNotification,
    object: watchedTextView)

However, be careful with that:

  • it's likely you only want to call that once, so do not call it in, for example, layoutSubviews

  • it's quite difficult to know when to best call it during your bring-up process. It will depend on your situation. Unfortunately there is no standard, locked-in solution

  • for example you usually certainly can not call it at init time, since of course watchedTextView may not exist yet

.

  1. the next problem is that ...

None of the notifications are called when text is changed programmatically.

This is a huge, age-old, and stupid, nuisance in iOS engineering.

Controls simply do not - end of story - call the notifcations when the .text property is changed programmatically.

This is insanely annoying because of course - obviously - every app ever made sets the text programmatically, such as clearing the field after the user posts, etc.

You have to subclass the text view (or similar control) like this:

class NonIdioticTextView: UIITextView {

    override var text: String! {
        // boilerplate code needed to make watchers work properly:
        get {
            return super.text
        }
        set {
            super.text = newValue
            NotificationCenter.default.post(
                name: UITextView.textDidChangeNotification,
                object: self)
        }

    }

}

(Tip - don't forget the super call has to come before ! the post call.)

There is no solution available, unless, you fix the control by subclassing as shown just above. That is the only solution.

Note that the notification

UITextView.textDidChangeNotification

results in

func textViewDidChangeSelection(_ textView: UITextView) 

being called.

(Not textViewDidChange .)

Fattie
  • 30,632
  • 54
  • 336
  • 607
1
[[NSNotificationCenter defaultCenter] addObserver:self 
selector:@selector(didChangeTextViewText:) 
name:UITextFieldTextDidChangeNotification object:nil];



- (void) didChangeTextViewText {
 //do something
}
PeiweiChen
  • 413
  • 5
  • 13
1

Starting iOS 14 there is no need to make a selector, you can pass a UIAction with a closure instead:

let editingChanged = UIAction { _ in
    // Do something when text changes...
}
myTextField.addAction(editingChanged, for: .editingChanged)
Aviel Gross
  • 8,880
  • 2
  • 47
  • 58
0

One thing is you may have multiple UITextFields. So, give them a tag and then you can switch on the tags. Here's how to setup an observer in any class pretty much.

private func setupTextFieldNotification() {
    NotificationCenter.default.addObserver(forName: UITextField.textDidChangeNotification, object: nil, queue: OperationQueue.main) { (notification) in
        if let textField = notification.object as? UITextField, textField.tag == 100, let text = textField.text {
            print(#line, text)
        }
    }
}

deinit {
    NotificationCenter.default.removeObserver(self)
}
smileBot
  • 18,797
  • 7
  • 60
  • 62
0

SwiftUI

If you are using the native SwiftUI TextField or just using the UIKit UITextField (here is how), you can observe for text changes like:

SwiftUI 2.0

From iOS 14, macOS 11, or any other OS contains SwiftUI 2.0, there is a new modifier called .onChange that detects any change of the given state:

struct ContentView: View {
    @State var text: String = ""

    var body: some View {
        TextField("Enter text here", text: $text)
            .onChange(of: text) {
                print($0) // You can do anything due to the change here.
                // self.autocomplete($0) // like this
            }
    }
}

SwiftUI 1.0

For older iOS and other SwiftUI 1.0 platforms, you can use onReceive with the help of the combine framework:

import Combine
.onReceive(Just(text)) { 
    print($0)
}

Note that you can use text.publisher instead of Just(text) but it returns the change instead of the entire value.

Mojtaba Hosseini
  • 47,708
  • 12
  • 157
  • 176
-1

Swift 4 Version

Using Key-Value Observing Notify objects about changes to the properties of other objects.

var textFieldObserver: NSKeyValueObservation?

textFieldObserver = yourTextField.observe(\.text, options: [.new, .old]) { [weak self] (object, changeValue) in
  guard let strongSelf = self else { return }
  print(changeValue)
}
Valeriy
  • 681
  • 6
  • 16
  • Good idea. can you wrap this in a context and let us know? – Revanth Kausikan May 02 '19 at 09:31
  • 1
    This is, unfortunately, totally wrong. KVO does NOT work in iOS for controls: http://stackoverflow.com/a/6352525/1402846 https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/KVO.html – Fattie Jan 20 '20 at 18:18