In my application there are many UISwitches and UITextFields displayed in a list of UITableViewCells.
When the user starts editing a UITextField and then taps on a UISwitch the order of the events causes the UITextField to display the value of the UISwitch because the event handler did not receive the end editing event of the UITextField.
How to ensure reliably that the UIControlEventEditingDidEnd event of the UITextField gets fired before the UIControlEventValueChanged of the UISwitch?
It leads to errors like this (value of switch displayed in text field):
Steps (what should happen):
1.Tap the UISwitch to activate it
UISwitch:startEditing:switch243
UISwitch:valueChanged:{true}
UISwitch:endEditing
UIEventHandler:saveValue:switch243:{true}
2.Tap the UITextField to start editing it
UITextField:startEditing:textfield455
3.Tap the UISwitch to deactivate it
UITextField:endEditing
UISwitch:startEditing:switch243
UISwitch:valueChanged:{false}
UISwitch:endEditing
UIEventHandler:saveValue:switch243:{false}
Console log (what really happens - the UISwitch event fires before 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
Implementation:
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 that aggregates all UI editing events:
-(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;
}

My best solution yet was to solve the problem in
UIEventHandler.m. If at the time of callingstartEditingtheendEditingevent wasn't triggered yet it gets called from theUIEventHandler.