You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在自定义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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.12 03:59:42