如何在自定义UIPicker类中获取UITextField的textFieldDidEndEditing回调
自定义MembershipPickerView优化方案解答
针对你提出的两个关于自定义UIPickerView的优化问题,我整理了以下可行的实现方案,全程不需要修改UITextField的代理指向,也不用在ViewController里写额外的冗余代码:
问题1:将选中值获取逻辑移至Picker类内部,无需设置TextField代理为Picker
完全可以实现!核心思路是利用NSNotificationCenter监听文本框的编辑结束通知,这种方式直接且易于维护。我们可以在自定义Picker的初始化方法里注册通知,当文本框结束编辑时,自动在Picker内部获取选中值并处理。
修改后的MembershipPickerView代码:
MembershipPickerView.h
#import <UIKit/UIKit.h> @class Membership; @interface MembershipPickerView : UIPickerView <UIPickerViewDelegate,UIPickerViewDataSource> @property (strong, nonatomic) NSArray <Membership *> *membershipData; // 可选:如果需要把选中值回调给外部,添加Block属性 @property (nonatomic, copy) void(^didSelectMembership)(Membership *selectedMembership); @end
MembershipPickerView.m
#import "MembershipPickerView.h" #import "Membership.h" @implementation MembershipPickerView{ NSArray <Membership *> *_membershipData; } -(instancetype)init{ self = [super init]; if (self) { self.dataSource = self; self.delegate = self; [self updateDataSource]; // 注册文本框编辑结束的通知 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleTextFieldEndEditing:) name:UITextFieldTextDidEndEditingNotification object:nil]; } return self; } - (void)dealloc{ // 移除通知避免内存泄漏 [[NSNotificationCenter defaultCenter] removeObserver:self]; } - (void)updateDataSource{ Membership *m1 = [Membership new]; m1.mName = @"M1"; m1.mId = @"1"; Membership *m2 = [Membership new]; m2.mName = @"M2"; // 修正了你原代码里的赋值错误 m2.mId = @"2"; _membershipData = @[m1,m2]; self.membershipData = _membershipData; [self reloadAllComponents]; } // 处理文本框编辑结束的逻辑 - (void)handleTextFieldEndEditing:(NSNotification *)notification{ UITextField *textField = notification.object; // 确认当前Picker是这个文本框的inputView if (textField.inputView == self) { NSInteger selectedIndex = [self selectedRowInComponent:0]; if (selectedIndex < _membershipData.count) { Membership *selectedMember = _membershipData[selectedIndex]; NSLog(@"选中的会员:%@,ID:%@", selectedMember.mName, selectedMember.mId); // 若需回调给外部ViewController,执行Block if (self.didSelectMembership) { self.didSelectMembership(selectedMember); } } } } #pragma mark - UIPickerView DataSource & Delegate - (NSInteger)numberOfComponentsInPickerView:(nonnull UIPickerView *)pickerView { return 1; } - (NSInteger)pickerView:(nonnull UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component { return _membershipData.count; } -(NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component{ return _membershipData[row].mName; } @end
ViewController代码简化:
@implementation vc1 { UITextField *textField1; MembershipPickerView *picker; } -(void)viewDidLoad { [super viewDidLoad]; picker = [MembershipPickerView new]; textField1.inputView = picker; // 可选:若需在VC中获取选中值,设置Block回调 picker.didSelectMembership = ^(Membership *selectedMembership) { textField1.text = selectedMembership.mName; }; // 不再需要设置textField1.delegate = self,也不用编写textFieldDidEndEditing方法! } @end
这样一来,获取选中值的逻辑完全封装在Picker内部,ViewController只需将Picker设为TextField的inputView即可。
问题2:在Picker类中获取TextField的resign事件,无需VC额外代码
同样可以通过通知中心实现,UITextFieldTextDidEndEditingNotification通知会在TextField resign第一响应者时触发,刚才问题1的代码已经包含了这个逻辑——handleTextFieldEndEditing:方法就是在TextField resign时执行的。
如果需要更精准地监听resign事件,也可以用KVO监听TextField的isFirstResponder属性,不过通知的方式更简洁:
补充KVO实现方式(可选)
在MembershipPickerView的init方法里,遍历父视图找到关联的TextField并添加KVO:
-(instancetype)init{ self = [super init]; if (self) { // ... 原有初始化代码 // 延迟执行确保Picker已被设置为TextField的inputView dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self findAssociatedTextFieldAndObserve]; }); } return self; } - (void)findAssociatedTextFieldAndObserve{ UIView *superView = self.superview; while (superView) { if ([superView isKindOfClass:[UITextField class]]) { UITextField *textField = (UITextField *)superView; // 监听isFirstResponder属性 [textField addObserver:self forKeyPath:@"isFirstResponder" options:NSKeyValueObservingOptionNew context:nil]; break; } superView = superView.superview; } } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{ if ([keyPath isEqualToString:@"isFirstResponder"]) { BOOL isFirstResponder = [[change objectForKey:NSKeyValueChangeNewKey] boolValue]; if (!isFirstResponder) { // TextField已resign,执行自定义逻辑 NSInteger selectedIndex = [self selectedRowInComponent:0]; if (selectedIndex < _membershipData.count) { Membership *selectedMember = _membershipData[selectedIndex]; NSLog(@"TextField resign,选中的会员:%@", selectedMember.mName); } } } } - (void)dealloc{ // 移除KVO监听 UIView *superView = self.superview; while (superView) { if ([superView isKindOfClass:[UITextField class]]) { [superView removeObserver:self forKeyPath:@"isFirstResponder"]; break; } superView = superView.superview; } // 移除通知 [[NSNotificationCenter defaultCenter] removeObserver:self]; }
不过这种方式需要处理视图层级遍历,相比通知方案稍复杂,推荐优先使用第一种通知实现方式。
内容的提问来源于stack exchange,提问作者Kuldeep Tanwar
相关产品推荐
相关产品推荐

