5

我希望能够更改具有 startDate 和 endDate 的(日历)事件。还有一个持续时间。

  1. 当用户更改 endDate 时,持续时间会更新。
  2. 当用户更改 startDate 时,endDate 会根据持续时间而更改。
  3. 当用户更改持续时间时,endDate 会更改。

这最后一个动作将触发第一个动作,这将触发第三个动作,这将触发第一个动作,无限期(或当堆栈填满时)。

像下面这样的行,为了改变值,会导致这个循环:

        [self setValue:[NSNumber numberWithLong:(interval%60)] forKeyPath:@"durationMinutes"];
        [self setValue:ed forKeyPath:@"endDate"];

简单地停止观察并在更改后重新启动是没有吸引力的,因为 GUI 中的值不会得到更新。那么,问题是:如何安全(优雅地)更新两个相互依赖的属性之一?

4

4 回答 4

6

您可以在必要时使用 绕过 KVO 通知setPrimitiveValue:forKey:。这会设置值但不会触发任何通知。它还绕过了您可能拥有的属性的任何自定义设置器。这应该会打破通话周期。

使用此方法时,您通常希望调用“将更改”和“已更改”方法以确保维持核心数据状态。也就是说,类似:

[self willChangeValueForKey:@"endDate"];
[self setPrimitiveValue:ed forKey:@"endDate"];
[self didChangeValueForKey:@"endDate"];

这避免了对标志的需要——你只是说,嘿,设置值,不要乱来,好吗?

于 2013-01-09T23:09:58.817 回答
3

最好的方法是编写自定义设置器,以便它们仅在值发生更改时更新值:

例如:

- (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;
    }
}

所以现在,当您将分钟设置为不同的值时,它将更新并设置结束日期。结束日期会有所不同,因此它将更新并设置持续时间。但是,这一次持续时间将是相同的(因为之前已设置),并且不会尝试再次设置自己或结束日期。

它还可以使您的代码保持整洁,并清楚地了解哪些更改以及何时更改。

于 2013-01-19T18:03:24.363 回答
1

将这三种方法合二为一。

- (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"];
}

在你的设置器中调用该方法

-(void)setStartDate:(NSDate *)start
{
   [self updateEndDate:self.endDate startDate:start duration:self.duration];
}

KVO 合规性要求您指定手动发送属性通知

+ (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;
}
于 2013-01-09T22:02:23.830 回答
0

最后,事实证明我是在与系统作斗争。我现在即时计算持续时间值,并使用 IBActions 对更改采取行动。

- (IBAction)changedEndDate:(id)sender
    if ( [_occurrence.endDate timeIntervalSinceDate:_occurrence.startDate] < 0 )
            _occurrence.startDate = _occurrence.endDate;

    long duration = ( [_occurrence.endDate timeIntervalSinceDate:_occurrence.startDate] ) / 60;
    [self.durationHoursTextField setStringValue:[NSString stringWithFormat:@"%ld",duration/60]];
    [self.durationMinutesTextField setStringValue:[NSString stringWithFormat:@"%ld",duration%60]];
}
- (IBAction)changedDurationOrStartDate:(id)sender
{
    long durH = [self.durationHoursTextField integerValue];
    long durM = [self.durationMinutesTextField integerValue];
    if ( durH < 0 )
    {
        durH = 0;
        [self.durationHoursTextField setStringValue:@"0"];
    }
    if ( durM < 0 )
    {
        durM = 0;
        [self.durationMinutesTextField setStringValue:@"0"];
    }
    long duration = durH * 3600 + durM * 60;
    _occurrence.endDate = [_occurrence.startDate dateByAddingTimeInterval:duration];
 }
于 2013-01-19T17:07:19.460 回答