如何监听UITextField代码设置文本操作?需排除用户输入触发
实现UITextField代码设置文本时的专属回调
这个需求我之前做项目时刚好碰到过,给你几个实用的实现思路,完美避开用户输入触发的情况:
思路一:子类化UITextField重写setText:方法
这是最直接且风险最低的方案。因为代码设置text属性时,系统最终会调用setText:方法,而用户通过键盘输入时,系统是通过insertText:、deleteBackward:这类文本输入相关方法来更新内容的,不会触发setText:。
你可以自定义一个子类,添加回调block或者代理方法:
@interface CustomTextField : UITextField // 定义回调block,代码设置文本时触发 @property (nonatomic, copy) void (^textSetByCodeHandler)(NSString *newText); @end @implementation CustomTextField - (void)setText:(NSString *)text { // 先调用父类方法更新文本 [super setText:text]; // 触发自定义回调 if (self.textSetByCodeHandler) { self.textSetByCodeHandler(text); } } @end
使用的时候直接实例化CustomTextField,绑定回调即可:
CustomTextField *textField = [[CustomTextField alloc] initWithFrame:CGRectMake(20, 100, 300, 40)]; textField.textSetByCodeHandler = ^(NSString *newText) { NSLog(@"文本被代码设置为:%@", newText); }; [self.view addSubview:textField];
思路二:KVO结合状态标记区分来源
如果不想子类化,可以用KVO监听text属性,但需要手动标记“是否是代码设置”的状态,避免把用户输入的变化也当成代码触发的。
示例代码:
@interface YourViewController () @property (nonatomic, assign) BOOL isSettingTextManually; @property (weak, nonatomic) IBOutlet UITextField *someTextField; @end @implementation YourViewController - (void)viewDidLoad { [super viewDidLoad]; // 注册KVO监听 [self.someTextField addObserver:self forKeyPath:@"text" options:NSKeyValueObservingOptionNew context:nil]; } // 封装代码设置文本的方法,统一标记状态 - (void)updateTextFieldText:(NSString *)newText { self.isSettingTextManually = YES; self.someTextField.text = newText; self.isSettingTextManually = NO; } // KVO回调 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context { if ([keyPath isEqualToString:@"text"] && object == self.someTextField) { if (self.isSettingTextManually) { NSString *newText = change[NSKeyValueChangeNewKey]; NSLog(@"文本被代码设置为:%@", newText); // 这里执行你的业务逻辑 } } } - (void)dealloc { // 记得移除KVO监听,避免野指针 [self.someTextField removeObserver:self forKeyPath:@"text"]; } @end
这个方案的缺点是所有代码设置文本的地方都必须通过你封装的updateTextFieldText:方法,否则会漏掉回调,适合小范围使用的场景。
思路三:Runtime方法交换(全局生效)
如果希望项目中所有UITextField的代码设置操作都能触发回调,且不想修改现有代码,可以用Runtime的方法交换技术,替换系统的setText:方法。
示例代码:
#import <objc/runtime.h> @implementation UITextField (TextSetHook) + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ SEL originalSel = @selector(setText:); SEL swizzledSel = @selector(hooked_setText:); Method originalMethod = class_getInstanceMethod(self, originalSel); Method swizzledMethod = class_getInstanceMethod(self, swizzledSel); // 先尝试添加原方法,避免分类覆盖原有方法 BOOL didAdd = class_addMethod(self, originalSel, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); if (didAdd) { class_replaceMethod(self, swizzledSel, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); } else { method_exchangeImplementations(originalMethod, swizzledMethod); } }); } - (void)hooked_setText:(NSString *)text { // 先调用原方法(因为方法交换了,这里调用hooked_setText实际是调用系统的setText:) [self hooked_setText:text]; // 发送自定义通知,或者执行回调逻辑 [[NSNotificationCenter defaultCenter] postNotificationName:@"UITextFieldTextSetByCode" object:self userInfo:@{@"newText": text ?: @""}]; } @end
使用时,只要在需要监听的地方注册通知即可:
- (void)viewDidLoad { [super viewDidLoad]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textFieldSetByCode:) name:@"UITextFieldTextSetByCode" object:nil]; } - (void)textFieldSetByCode:(NSNotification *)note { UITextField *textField = note.object; NSString *newText = note.userInfo[@"newText"]; NSLog(@"文本框%@被代码设置为:%@", textField, newText); } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; }
这个方案要注意:方法交换可能和其他第三方库的逻辑冲突,上线前一定要充分测试,适合需要全局监听的场景。
内容的提问来源于stack exchange,提问作者Kex
相关产品推荐
相关产品推荐

