【问题标题】:UISwitch value changed event fires before UITextField editing did end在 UITextField 编辑结束之前触发 UISwitch 值更改事件
【发布时间】:2019-04-23 00:28:15
【问题描述】:

在我的应用程序中,UITableViewCells 列表中显示了许多 UISwitches 和 UITextFields。

当用户开始编辑 UITextField 然后点击 UISwitch 时,事件的顺序会导致 UITextField 显示 UISwitch 的值,因为事件处理程序没有收到结束编辑事件UITextField 的。

如何可靠地确保UITextFieldUIControlEventEditingDidEnd 事件在UISwitchUIControlEventValueChanged 之前被触发?

它会导致这样的错误(文本字段中显示的开关值):

步骤(应该发生什么):

1.点击UISwitch激活它

UISwitch:startEditing:switch243
UISwitch:valueChanged:{true}
UISwitch:endEditing
UIEventHandler:saveValue:switch243:{true}

2.点击UITextField开始编辑

UITextField:startEditing:textfield455

3.点击UISwitch将其停用

UITextField:endEditing
UISwitch:startEditing:switch243
UISwitch:valueChanged:{false}
UISwitch:endEditing
UIEventHandler:saveValue:switch243:{false}

控制台日志(实际发生的情况 - UISwitch 事件在 UITextField:endEditing 之前触发):

UISwitch:startEditing:switch243
UISwitch:valueChanged:{true}
UISwitch:endEditing
UIEventHandler:saveValue:switch243:{true}
UITextField:startEditing:textfield455
UISwitch:startEditing:switch243
UISwitch:valueChanged:{false}
UISwitch:endEditing
UIEventHandler:saveValue:switch243:{false}
UITextField:endEditing

实施:

UITableViewCellWithSwitch.h:

@interface UITableViewCellWithSwitch : UITableViewCell
@property (nonatomic, strong) NSString *attributeID;
@property (nonatomic, retain) IBOutlet UISwitch *switchField;
@end

UITableViewCellWithSwitch.m:

@implementation UITableViewCellWithSwitch
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (self) {
        [self.switchField addTarget:self
                            action:@selector(switchChanged:)
                  forControlEvents:UIControlEventValueChanged];
    }
    return self;
}
// UIControlEventValueChanged
- (void)switchChanged:(UISwitch *)sender {
    NSLog(@"UISwitch:startEditing:%@",self.attributeID);
    [self handleStartEditingForAttributeID:self.attributeID];

    NSString* newValue = sender.on==YES?@"true":@"false";
    NSLog(@"UISwitch:valueChanged:{%@}", newValue);
    [self handleValueChangeForEditedAttribute:newValue];

    NSLog(@"UISwitch:endEditing");
    [self handleEndEditingForEditedAttribute];
}
@end

UITableViewCellWithTextField.h:

@interface UITableViewCellWithTextField : UITableViewCell<UITextFieldDelegate>
@property (nonatomic, strong) NSString *attributeID;
@property (strong, nonatomic) IBOutlet UITextField *inputField;
@end

UITableViewCellWithTextField.m:

@implementation UITableViewCellWithTextField
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (self) {
        [self.inputField addTarget:self
                            action:@selector(textFieldDidBegin:)
                  forControlEvents:UIControlEventEditingDidBegin];

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

        [self.inputField addTarget:self
                            action:@selector(textFieldDidEnd:)
                  forControlEvents:UIControlEventEditingDidEnd];
    }
    return self;
}
// UIControlEventEditingDidBegin
-(void) textFieldDidBegin:(UITextField *)sender {
    NSLog(@"UITextField:startEditing:%@",self.attributeID);
    [self handleStartEditingForAttributeID:self.attributeID];
}
// UIControlEventEditingChanged
-(void) textFieldDidChange:(UITextField *)sender {
    NSLog(@"UITextField:valueChanged:{%@}", sender.text);
    [self handleValueChangeForEditedAttribute:sender.text];
}
// UIControlEventEditingDidEnd
-(void) textFieldDidEnd:(UITextField *)sender {
    NSLog(@"UITextField:endEditing");
    [self handleEndEditingForEditedAttribute];
}
@end

UIEventHandler.m 聚合所有 UI 编辑事件:

-(void) handleStartEditingForAttributeID:(NSString *)attributeID { 
    // Possible solution   
    //if (self.editedAttributeID != nil && [attributeID isEqualToString:self.editedAttributeID]==NO) { // Workaround needed for UISwitch events
    //    [self handleEndEditingForActiveAttribute];
    //}
    self.editedAttributeID = attributeID;
    self.temporaryValue = nil;
}

-(void) handleValueChangeForEditedAttribute:(NSString *)newValue {
    self.temporaryValue = newValue;
}

-(void) handleEndEditingForEditedAttribute { 
    if (self.temporaryValue != nil) { // Only if value has changed
        NSLog(@"UIEventHandler:saveValue:%@:{%@}", self.editedAttributeID, self.temporaryValue);

        // Causes the view to regenerate
        // The UITextField loses first responder status and UIControlEventEditingDidEnd is gets triggered too late
        [self.storage saveValue:self.temporaryValue 
                   forAttribute:self.editedAttributeID];

        self.temporaryValue = nil;
    }
    self.editedAttributeID = nil;
}

【问题讨论】:

  • 您需要提供有关您的代码在做什么的更多信息。从您所显示的内容来看,点击开关以“停用它”除了执行两个日志语句之外没有任何作用。见minimal reproducible example
  • 你想在Switch上实现什么?
  • @MohammadSadiq 我需要按UIEventHandler.m使用的每个输入字段类型(开始编辑、更改值、结束编辑)调用 3 个事件
  • @PeterGerhat - 当您只发布部分代码时,任何人都很难提供帮助。例如,现在你有两个textFieldDidBegin 方法?此外,没有任何迹象表明sender.attributeID 来自哪里?
  • @DonMag 在问题中添加了更多详细信息。这是一个关于 UIKit 中的事件处理的问题,特别是 UISwitch 和 UITextField。不想让它太宽泛。

标签: ios uitextfield uikit uiswitch uicontrolevents


【解决方案1】:

如果我理解正确,您遇到的问题是当文本字段是第一响应者时更改开关值,然后您的文本字段的文本会更新为开关的值。

UITextFielddidEndEditing: 事件仅在文本字段退出第一响应者时发生。如果您只想确保当开关值更改时文本字段结束编辑,您应该在开关收到UIControlEventValueChanged 事件时将endEditing: 消息发送到活动文本字段。

现在,如何在文本字段中调用 endEditing: 消息取决于类的结构。您可以在表格视图单元格类上指定一个初始化方法,在该方法中传递与控制文本字段的UISwitch 对应的UITextField 实例。保持对文本字段实例的弱引用,然后在开关值更改时调用endEditing:。或者,当switchChanged: 事件被触发或[self.superview endEditing:YES]; 时,您可以简单地尝试在UITableViewCellWithSwitch 上调用[self endEditing:YES];。我更喜欢前一种解决方案而不是后者,因为后者更像是一种 hack 而不是正确的解决方案。

更新:

查看您的代码后,您在问题中提到的错误原因

它会导致这样的错误(文本字段中显示的开关值):

是下面这段代码:

- (void)switchChanged:(UISwitch *)sender {
    NSLog(@"UISwitch:startEditing:%@",self.attributeID);
    [self handleStartEditingForAttributeID:self.attributeID];

    NSString* newValue = sender.on==YES?@"true":@"false";
    NSLog(@"UISwitch:valueChanged:{%@}", newValue);
    [self handleValueChangeForEditedAttribute:newValue];

    NSLog(@"UISwitch:endEditing");
    [self handleEndEditingForEditedAttribute];
}

您正在调用handleValueChangeForEditedAttribute:,并使用该属性的开关值,该属性实际上只应该保存文本字段的值。在您的UIEventHandler 类中,您正在使用handleEndEditingForEditedAttribute 方法中的开关值更新您的数据访问对象(DAO)。将您的 switchChanged: 方法的逻辑更改为如下所示:

- (void)switchChanged:(UISwitch *)sender {
    if (sender.on == YES) {
        [self handleStartEditingForAttributeID:self.attributeID];
    } else {
        [self handleEndEditingForEditedAttribute];
    }
}

在您的UIEventHandler 课程中,取消注释您帖子中“可能的解决方案”的注释行。这应该确保在为新的attributeID 存储值之前保存之前的任何更改。

-(void) handleStartEditingForAttributeID:(NSString *)attributeID { 
    // Possible solution   
    if (self.editedAttributeID != nil && [attributeID isEqualToString:self.editedAttributeID]==NO) { // Workaround needed for UISwitch events
        [self handleEndEditingForActiveAttribute];
    }
    self.editedAttributeID = attributeID;
    self.temporaryValue = nil;
}

【讨论】:

  • 没错[self.storage saveValue] 会导致整个视图重新生成,这会导致UITextField 失去第一响应者状态并在为时已晚时触发UIControlEventEditingDidEnd。我开始认为,由于我的应用程序的架构,解决它的最佳位置是UIEventHandler.m(请参阅可能的解决方案)。
  • @PeterGerhat 你是对的。查看我更新的答案。我更新了你的switchChanged:
  • 您似乎不知道switchChanged: 应该如何工作。它必须按该顺序触发startEditingvalueChangedendEditing 事件,UIEventHandler 才能正常工作。我在许多不同的 UI 输入组件中使用此事件模型,因此我无法将其专门用于 UISwitch
  • @PeterGerhat Ah 明白了。好吧,如果这种设计在您的应用程序中是通用的,我会说您最好的选择是您在下面发布的解决方案,或者在您的表格视图单元格或其父视图中调用 [self.view endEditing:YES] 以触发 UITextField 的确实结束编辑事件。
【解决方案2】:

我最好的解决方案是解决UIEventHandler.m 中的问题。如果在调用startEditing 时,endEditing 事件尚未触发,它会从UIEventHandler 调用。

-(void) handleStartEditingForAttributeID:(NSString *)attributeID { 
    // Possible solution   
    if (self.editedAttributeID != nil && [attributeID isEqualToString:self.editedAttributeID]==NO) { // Workaround needed for UISwitch events
        [self handleEndEditingForActiveAttribute];
    }
    self.editedAttributeID = attributeID;
    self.temporaryValue = nil;
}

-(void) handleValueChangeForEditedAttribute:(NSString *)newValue {
    self.temporaryValue = newValue;
}

-(void) handleEndEditingForEditedAttribute { 
    if (self.temporaryValue != nil) { // Only if value has changed
        NSLog(@"UIEventHandler:saveValue:%@:{%@}", self.editedAttributeID, self.temporaryValue);

        // Causes the view to regenerate
        // The UITextField loses first responder status and UIControlEventEditingDidEnd is gets triggered too late
        [self.storage saveValue:self.temporaryValue 
                   forAttribute:self.editedAttributeID];

        self.temporaryValue = nil;
    }
    self.editedAttributeID = nil;
}

【讨论】:

    猜你喜欢
    • 2015-10-13
    • 1970-01-01
    • 1970-01-01
    • 2015-08-18
    • 1970-01-01
    • 1970-01-01
    • 2015-08-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多