UITextField text change event
Asked Answered
M

22

642

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;
}
Morbidity answered 10/8, 2011 at 12:9 Comment(1)
Swift versionShoshonean
V
1154

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.

Vachel answered 10/8, 2011 at 12:21 Comment(15)
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 codeZumwalt
The only issue is. It doesn't work for me together with - (BOOL) textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)stringJamin
@Jamin 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.Justinejustinian
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?Supermarket
If you use the UITextInput protocol methods to change the text the action gets triggered. So just don't do textField.text = newText :)Edifice
what's the signature of the textFieldDidChange method?Naman
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 changesJoyance
@Joyance you could use textFieldDidEndEditing?Narcotism
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.Joyance
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?Pinzler
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 closuresStidham
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)Poniard
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.Pompadour
This is simply wrong. It's a basic nuisance in iOS that controls DO NOT emit that event in the case of programmatic changes.Interpellant
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 typingMariann
V
417

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

Vermiculate answered 5/12, 2012 at 17:36 Comment(7)
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.Conchiferous
I just checked it in 6.3 and it's there. This is a UITextView and right click on it and see "Editing Changed".Vermiculate
why on earth is it named Editing Changed?? mad! thanks for the solution though :-)Ulund
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.Fantinlatour
I notice this doesn't work as user tabs out of the textfield and an autocorrect text replaces the contentBarathea
For Obj C if you are going to connect outlet from NIB the method should contain IBAction as follow; - (IBAction)textFieldDidChange:(UITextField *)textField {}Sialkot
Does this require subclassing the UITextField? Can't assign that action to the parent view controller via Xcode?Zitella
D
142

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);
}
Deuced answered 6/6, 2013 at 8:25 Comment(1)
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!Plascencia
S
118

Swift:

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

Then, implement the callback function:

@objc final private func yourHandler(textField: UITextField) {
    print("Text changed")
}
Shoshonean answered 14/3, 2016 at 22:0 Comment(2)
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).Gulley
Add _ before textField in textFieldDidChange like this: func textFieldDidChange(textField: UITextField) { ... }Mert
B
34

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://mcmap.net/q/65081/-what-39-s-wrong-with-this-observevalueforkeypath-ofobject-change-context-implementation

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.

Bledsoe answered 19/2, 2013 at 17:7 Comment(4)
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.Myocardium
That would seem to be intended behaviour. No other delegate methods (e.g. scrolling, selection) get called for events originated by code.Myocardium
Note that UIKit is not guaranteed to be KVO compliant, so the KVO solution won’t necessarily work.Resht
@Myocardium , 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.Interpellant
A
30

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

enter image description here

Analemma answered 22/8, 2016 at 9:30 Comment(0)
F
15

Here in swift version for same.

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

func textFieldDidChange(textField: UITextField) {

}

Thanks

Footbridge answered 1/10, 2015 at 7:2 Comment(0)
H
15

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)
Hobbledehoy answered 22/1, 2021 at 4:4 Comment(0)
C
13

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
}
Culmination answered 11/11, 2013 at 15:30 Comment(2)
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.Diaghilev
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.Lycia
C
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!)

}
Creativity answered 25/4, 2016 at 8:45 Comment(0)
C
8

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!)

}
Crosspurpose answered 8/11, 2017 at 1:24 Comment(0)
O
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!) {

    }
}
Octopus answered 4/9, 2016 at 16:31 Comment(0)
I
5
  1. KVO solutions do not work

KVO does NOT work in iOS for controls: https://mcmap.net/q/65082/-does-key-value-observing-work-on-uitextview-39-s-text-property 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 .)

Interpellant answered 20/1, 2020 at 18:33 Comment(0)
A
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.

Anamorphism answered 4/12, 2017 at 6:58 Comment(0)
P
3

Swift 3.1:

Selector: ClassName.MethodName

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

  func fieldChanged(textfieldChange: UITextField){

    }
Punch answered 11/4, 2017 at 11:32 Comment(0)
B
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.

Borchert answered 25/4, 2016 at 6:42 Comment(0)
S
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.

Sharika answered 6/6, 2017 at 2:36 Comment(2)
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?Spiffy
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.Sharika
C
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
 }
Conner answered 21/6, 2017 at 14:46 Comment(0)
C
1
[[NSNotificationCenter defaultCenter] addObserver:self 
selector:@selector(didChangeTextViewText:) 
name:UITextFieldTextDidChangeNotification object:nil];



- (void) didChangeTextViewText {
 //do something
}
Cassilda answered 4/1, 2016 at 6:57 Comment(0)
O
1

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)
}
Ochoa answered 2/5, 2020 at 16:51 Comment(0)
L
1

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.

Leenaleeper answered 4/11, 2020 at 7:49 Comment(0)
P
-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)
}
Phosphorescent answered 19/3, 2019 at 15:11 Comment(2)
Good idea. can you wrap this in a context and let us know?Ploch
This is, unfortunately, totally wrong. KVO does NOT work in iOS for controls: https://mcmap.net/q/65082/-does-key-value-observing-work-on-uitextview-39-s-text-property developer.apple.com/library/archive/documentation/General/…Interpellant

© 2022 - 2024 — McMap. All rights reserved.