UISwitch 值更改事件在 UITextField 编辑结束之前触发

UISwitch value changed event fires before UITextField editing did end

在我的应用程序中,UITableViewCell 列表中显示了许多 UISwitchUITextField

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

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

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

步骤(应该发生什么):

1.Tap UISwitch 激活它

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

2.Tap UITextField 开始编辑吧

UITextField:startEditing:textfield455

3.Tap 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;
}

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

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

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

更新:

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

It leads to errors like this (value of switch displayed in text field):

是下面的一段代码:

- (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 class 中,您正在使用 handleEndEditingForEditedAttribute 方法中的开关值更新您的数据访问对象 (DAO)。将您的 switchChanged: 方法的逻辑更改为如下内容:

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

并且在您的 UIEventHandler class 中,取消注释 post 中的 "possible solution" 注释行。这应该确保在存储新 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;
}

我最好的解决方案是解决 UIEventHandler.m 中的问题。如果在调用 startEditingendEditing 事件尚未触发,它会从 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;
}