I want to be able to change a (calendar) event, which has a startDate and an endDate. There's also a duration.
This last action would trigger the first action, which would trigger the third, which would trigger the first, ad infinitum (or when the stack fills up).
Lines like the following, to change the values, cause this loop:
[self setValue:[NSNumber numberWithLong:(interval%60)] forKeyPath:@"durationMinutes"];
[self setValue:ed forKeyPath:@"endDate"];
To simply stop observing, and restart after a change, is not attractive since the values in the GUI won't get updated. The question, then, is: How can I safely (and elegantly) update one of two interdependent properties?
You can bypass KVO notifications when necessary by using setPrimitiveValue:forKey:
. That sets the value but does not trigger any notifications. It also bypasses any custom setter you might have for the property. That should break the call cycle.
When using this method you generally want to call the "will change" and "did change" methods to ensure that the Core Data state is maintained. That is, something like:
[self willChangeValueForKey:@"endDate"];
[self setPrimitiveValue:ed forKey:@"endDate"];
[self didChangeValueForKey:@"endDate"];
This avoids the need for a flag-- you just say, hey, set the value and no messing around, OK?
The best way would be to write custom setters so that they update the values only if they have changed:
For instance:
- (void)setDurationMinutes:(NSNumber *)minutes
{
if (![minutes isEqual:self.durationMinutes])
{
_durationMinutes = minutes;
self.endDate = [self.startDate dateByAddingTimeInterval:minutes * 60];
}
}
- (void)setEndDate:(NSDate *)date
{
if (![_endDate isEqualToDate:date])
{
_endDate = date;
self.durationMinutes = [date timeIntervalSinceDate:self.startDate] / 60;
}
}
So now, when you set the minutes to a different value, it will update and set the end date. End date will be different so it will update and set the duration. However, this time the duration will be the same (since it was previously set) and it won't attempt to set itself or the end date again.
It also keeps your code nice and clean, and clear about what changes what and when.
Combine all three into one method.
- (void)updateEndDate:(NSDate *)end
startDate:(NSDate *)start
duration:(NSNumber *)duration
{
... insert your update logic here
[self willChangeValueForKey:@"startDate"];
[self willChangeValueForKey:@"endDate"];
[self willChangeValueForKey:@"duration"];
_endDate = end;
_startDate = start;
_duration = duration;
[self didChangeValueForKey:@"duration"];
[self didChangeValueForKey:@"endDate"];
[self didChangeValueForKey:@"startDate"];
}
Call that method in your settors
-(void)setStartDate:(NSDate *)start
{
[self updateEndDate:self.endDate startDate:start duration:self.duration];
}
KVO compliance requires you to specify that you are manually sending notifications for the properties
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey
{
BOOL automatic = NO;
NSArray * manualKeys = @[@"endDate", @"startDate", @"duration"];
if ([manualKeys containsObject:theKey])
{
automatic = NO;
}
else
{
automatic = [super automaticallyNotifiesObserversForKey:theKey];
}
return automatic;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With