I'm currently re-writing a form controller for iOS. It's a custom object that is bound to a model, and handles editing form fields, jumping to the prev/next field, handling custom keyboards, validating data...
The first version was based on a plist for storing the form values, the form controller held all the data itself. Now I want to dissociate the storage (model) from the form controller, thus I've settled with using KVO.
For simplicity's sake, let's assume I've got a form designed to edit a time span for an absence. So it's got two fields: leaveDate
and returnDate
.
My model is as follows:
@interface Absence
@property (strong, nonatomic) NSDate *leaveDate;
@property (strong, nonatomic) NSDate *returnDate;
@property (readonly, nonatomic) BOOL isValid;
@end
My form controller has a property model
which points to this object.
When the user taps on the "leave date" text field in my XIB, the form controller hands in and presents a date picker based on the current value of my model’s leaveDate
. When the user picks some other date, the form controller updates its model by using setValue:forKey:
.
The isValid
property is declared as being impacted by leaveDate
and returnDate
(using +keyPathsForValuesAffectingIsValid
), and the form controller has registered for watching a change in this property, to enable/disable the submit button on the fly.
Up to this point, everything works like a charm. Now, for the twisted part:
I want my form controller to be able to handle changes in the model while it's open. Example: I've got a rule in the model that says "an absence must least at last 3 days". When the users changes the leave date, the return date is automatically adjusted if the total duration does not exceed 3 days.
So my form controller must also register for listening to changes in all properties. The problem is that it both changes the properties, and listens to changes.
That way, when the user changes leaveTime
, the form controller uses setValue:forKey:
to update the model, but instantly receives a KVO notification for this very change it has just made. This is unnecessary and potentially harmful (I just made the change myself, I don't need to be told I've just done it).
The only way around I found till now is un-registering just before setting the new value, then re-registering right after, like this:
[self.model removeObserver:self forKeyPath:self.currentField.key];
[self.model setValue:newValue forKey:self.currentField.key];
[self.model addObserver:self forKeyPath:self.currentField.key options:NSKeyValueObservingOptionNew context:nil];
It's working, but it's ugly, and performance-wise I doubt it's great.
Does somebody have an explanation as to how to do it better?
TL;DR
ControllerA
is a registered KVO observer of Model
.
ControllerB
updates Model
==> ControllerA
receives a KVO notification. That's fine.
ControllerA
updates Model
==> ControllerA
receives a KVO notification. I don't want this one.