UITextField文本改变事件

638
我该如何检测textField中的任何文本更改?委托方法shouldChangeCharactersInRange在某些情况下有效,但并不能完全满足我的需求。因为在它返回YES之前,其他观察者方法无法访问textField文本。
例如,在我的代码中,calculateAndUpdateTextFields没有获取到用户键入的更新文本。
有没有办法获得类似于Java事件处理程序中的textChanged事件处理程序?
- (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;
}

Swift版本 - Juan Boero
22个回答

1150

来自如何正确处理UITextField文本更改回调的方法

我像这样捕获发送到UITextField控件的字符:

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

在Objective-C中:

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

在Swift中:

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

textFieldDidChange 方法中,您可以检查 textField 的内容,并根据需要重新加载表格视图。

您可以使用该方法,并将 calculateAndUpdateTextFields 作为您的 selector


20
这是更好的解决方案-因为您还可以在Nibs或Storyboards中设置它,而且您不必编写过多的UITextFieldTextDidChangeNotification代码。 - PostCodeism
3
唯一的问题是,它与-(BOOL)textField:(UITextField*)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString*)string一起对我无效。 - iWheelBuy
6
只有当返回了NO时,逻辑才成立。因为从这个方法返回NO时,你基本上是在说该字段中的文本不应该改变。 - gitaarik
3
这对于编辑引起的更改非常有效。但它无法捕获通过 textField.text = "Some new value"; 发生的编程更改。有没有巧妙的方法来捕获这种更改? - Benjohn
18
你可能认为在苹果的 UITextFieldDelegate 中应该包含类似于 func textField: UITextField, didChangeText text: String 的内容,但是...(向苹果公司的工程师们投以不满的眼神) - Brandon A
显示剩余10条评论

417
的回答很准确。
您也可以在界面构建器中进行此操作,方法是右键单击UITextField并将"Editing Changed"发送事件拖动到您的子类单元中。 UITextField Change Event

2
虽然它可能很简单,但我在搜索了10-15分钟后仍然没有发现这种可能性,直到偶然间看到了你的答案。再加上我的一个 +1;像这样的问题既有基于代码的解决方案,又有基于IB的解决方案,都能得到高票是很好的。 - Mark Amery
我刚在6.3中检查了一下,它在那里。这是一个UITextView,在其上右键单击并查看"Editing Changed"。 - William T.
7
为什么它被命名为“Editing Changed”?太疯狂了!不过还是感谢你提供的解决方案 :-) - malhal
3
当编辑修改时,触发的事件称为 "UITextFieldTextDidChangeNotification";而 "Value Changed" 则是在文本字段完成编辑,即取消第一响应者时发生的。 "UITextFieldTextDidChangeNotification" 是 UITextField 的通知,而 "UIControlEventValueChanged" 是由 UIControl 实现的,其下属的 UIButton 也实现了该事件。 - Camsoft
3
当用户切换到文本框外并且自动更正文本替换内容时,我注意到这个不起作用了。 - noobular
显示剩余2条评论

142

设置事件监听器:

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

认真倾听:

- (void)textFieldDidChange:(UITextField *)textField {
    NSLog(@"text changed: %@", textField.text);
}

很酷。我喜欢这种方法,因为我想在一个已经是TextFieldDelegate的ViewController基类中实现textField-change-listening。这样我就可以像使用魔法一样为(subviews中的textField)添加addTarget:baseControllerMethod了。谢谢! - HBublitz

118

Swift:

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

接下来,实现回调函数:

@objc final private func yourHandler(textField: UITextField) {
    print("Text changed")
}

4
对我有用,我已经尝试过这个方法,但没有意识到函数需要一个textField参数。如果这个方法对你不起作用,请确保检查你的选择器是否有一个textField参数要传入(即使你不使用它)。 - werm098
在textFieldDidChange函数中,在textField之前添加下划线,像这样:func textFieldDidChange(textField: UITextField) { ... } - Makalele

34

如此指出:UITextField文本更改事件,似乎在iOS 6(已检查iOS 6.0和6.1)之后仅观察 UITextFieldTextDidChangeNotification 不能完全检测 UITextField 对象的更改。

现在,似乎只有内置的iOS键盘直接进行的更改才会被跟踪。这意味着,如果您仅通过调用以下命令更改 UITextField 对象: myUITextField.text = @“任何文本” ,则将不会收到任何更改通知。

我不知道这是一个错误还是一个意图。由于我在文档中没有找到任何合理的解释,因此似乎像个错误。这也在此处指出:UITextField文本更改事件

我对此的“解决方案”实际上是,对于我对 UITextField 所做的每个更改(如果该更改没有使用内置的iOS键盘),都会发布一个通知。类似于这样:

myUITextField.text = @"I'm_updating_my_UITextField_directly_in_code";

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

[NSNotificationCenter.defaultCenter 
  postNotification:myTextFieldUpdateNotification];

通过这种方式,您可以100%确定,在更改您的UITextField对象的.text属性时,无论是在代码中“手动”更新还是通过内置的iOS键盘,都将收到相同的通知。

需要注意的是,由于这不是一种文档记录的行为,因此这种方法可能会导致您的UITextField对象发生相同更改的情况下收到2个通知。根据您的需求(当UITextField.text更改时实际上要执行什么操作),这可能对您造成不便。

稍微不同的方法是,如果您确实需要知道通知是您的还是“iOS制作的”,则可以发布自定义通知(即,具有自定义名称而不是UITextFieldTextDidChangeNotification)。

编辑:

我刚找到了一种不同的方法,我认为这可能更好:

这涉及Objective-C的键值观察(KVO)功能(http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/KeyValueObserving/KeyValueObserving.html#//apple_ref/doc/uid/10000177-BCICJDHA)。

基本上,您可以将自己注册为属性的观察者,如果该属性发生更改,则会收到通知。 “原则”与NSNotificationCenter的工作方式非常相似,主要优点是这种方法也可以在iOS 6中自动工作(无需任何特殊调整,例如手动发布通知)。

对于我们的UITextField场景,如果您将此代码添加到包含文本字段的UIViewController中,则可以正常工作:

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

}

关于"上下文"管理的答案感谢: https://dev59.com/imw15IYBdhLWcg3wfr5x#12097161

注意:当您正在使用内置iOS键盘编辑UITextField时,似乎您输入或删除每个新字母时文本字段的"text"属性不会更新。相反,在您放弃文本字段的第一响应者状态之后,文本字段对象会整体更新。


6
为什么要让自己经历这个,只需使用XenElement回答中的目标-动作方法即可。如果你需要数十行代码来完成本应简单的任务,那么你很可能做错了。 - jrturton
6
这似乎是预期的行为。对于由代码发起的事件,其他委托方法(例如滚动,选择)不会被调用。 - jrturton
1
请注意,UIKit不能保证符合KVO,因此KVO解决方案不一定适用。 - Pang
@jrturton,关于你的“为什么”问题,完全普遍的情况是,在其他类(例如装饰、动画或类似的类)中,您需要响应文本字段中发生的情况。 - Fattie

30

我们可以轻松地在 Storyboard 中进行配置,通过按住 CTRL 拖动 @IBAction 并将事件更改如下:

输入图像描述


15

这是Swift版本的代码。

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

func textFieldDidChange(textField: UITextField) {

}

谢谢


15

从iOS 14开始,不必再创建一个选择器,可以直接使用带有闭包的UIAction

let editingChanged = UIAction { _ in
    // Do something when text changes...
}
myTextField.addAction(editingChanged, for: .editingChanged)

13

我通过更改shouldChangeChractersInRange的行为来解决了这个问题。如果您返回NO,则iOS不会内部应用更改,而是您有机会手动更改它并在更改后执行任何操作。

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

3
这个解决方案的问题在于:光标位置会被设置到字段的末尾(一旦你执行textField.text = ...)。因此,如果用户想要编辑字段中间的字符,那么每次按键都会使他们的光标跳到字段的末尾。试一下就知道了。 - FranticRock
@Alex 说得不错,但是可以很简单地解决。只需在本地 NSString 中计算出结果值,并将其传递给 didChangeTextInTextField:toText: 方法,然后返回 YES,让 iOS 执行更新即可。 - jk7

11

已测试的 Swift 版本:

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

参数说明:

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

然后在您的UIViewController中添加您上面创建的方法:

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

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

}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接