UITextField text change event

2018-12-31 08:49发布

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

18条回答
公子世无双
2楼-- · 2018-12-31 09:20

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.

查看更多
与君花间醉酒
3楼-- · 2018-12-31 09:20

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

}
查看更多
素衣白纱
4楼-- · 2018-12-31 09:25

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

    }
}
查看更多
千与千寻千般痛.
5楼-- · 2018-12-31 09:25

It's really simple with observer and reactive swift (RxCocoa & RxSwift).

Just subscribe to text property of rx, Like below:

myTextField.rx.text.subscribe { text in
   print("UITextFieldTextChangeEvent Text:\(text)")
}.disposed(by: disposeBag)
查看更多
看淡一切
6楼-- · 2018-12-31 09:27

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);
}
查看更多
看淡一切
7楼-- · 2018-12-31 09:31

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
}
查看更多
登录 后发表回答