Search code examples
objective-ccocoacocoa-bindingskey-value-observingkey-value-coding

Value binding on a slider don't update properly


the application I am currently working is controlling the position within a QuickTime movie with a normal NSSlider. Just as in every media player out there.

I wanted to bind the value of the slider to the movie position ( which is in a 0 to 1 range ) to avoid sticky / glue code.

When I drag the slider in the UI, the right setter get called, but for some reason the slider doesn't get updated when I change the value programmatically. I would say that I understand the principles of bindings and how KVC works. And I also know that you have to either use getters or setters or you have to notify the observers with [self willChangeValueForKey:@"theKey"] and [self didChangeValueForKey:@"theKey"] when you change the values programmatically.

For some reason this still doesn't work. I also have some log statements in the getter. It isn't even called once!

I can't really explain myself what the reason for this behavior might be. The only thing, that's a bit different from simple examples found online ( and in Aaron Hillegass's book ) is that I am using a key path instead of a simple key.

From IB, I go to the App Delegate, which has an activeMovie property. The activeMovie is an instance of class VS_Movie which has a moviePosition property. So my keypath is : self.activeMovie.moviePosition which Xcode also seems to find.

I would be grateful for any tips and help provided! Thanks in advance

Here's some code I am using :

1. Property declaration

// ivar
NSNumber* _moviePosition;

// Property
@property (retain) NSNumber* moviePosition;

2. Getter and setter implementation

- (NSNumber*) moviePosition
{
    NSLog(@"MoviePosition returned : %lf", [_moviePosition doubleValue]);
    return _moviePosition;
}

- (void) setMoviePosition:(NSNumber*)newPos
{
    NSLog(@"New MoviePosition : %lf", [newPos doubleValue]);

    if ( _moviePosition && [newPos isEqualToNumber:_moviePosition] )
        return;

    if ( _moviePosition ) 
       [_moviePosition release];

    _moviePosition = [newPos retain];

    if ( _positioningReady )
    {
        _positioningReady = NO;

        TimeValue newMovieTime = (TimeValue)([_moviePosition doubleValue] * 
                                             (double)_clipDurationTimeValue);
        SetMovieTimeValue(_qtMovie, newMovieTime);
        [_movieView displayCurrentFrameOfMovie:self];

        // Post a notification to inform the observing 
        // classes so they can do the neccessary handling.
        NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
        [nc postNotificationName:VS_MoviePositionChangedNotification 
                      object:self];
    }
}

3. Calling function

- (void) setTimeValue:(TimeValue) value withTimeScale:(TimeScale) scale
{
    SetMovieTimeValue(_qtMovie, value);
    SetMovieTimeScale(_qtMovie, scale);

    double newMoviePosition = (double)GetMovieTime(_qtMovie, NULL) / (double)_clipDurationTimeValue;
    [self setMoviePosition:[NSNumber numberWithDouble:newMoviePosition]];

    [_movieView displayCurrentFrameOfMovie:self];
}

Solution

  • Given that the property is called moviePosition, the getter should be called -moviePosition (no underscore) and the setter should be called -setMoviePosition: (no underscore and the first letter of the property name capitalized).

    You didn't show the @property declaration. While it is allowed to specify the names of the setter and the getter, neither KVC nor KVO pay attention to that. For them, it needs to follow the naming conventions. See Key-Value Coding Programming Guide: Accessor Search Patterns for Simple Attributes.

    Are you sure the right setter is being called as you claim? Because you have misnamed your accessor methods, the compiler doesn't recognize them as such. Therefore, the @synthesize is generating properly named ones. Those are being invoked by KVC (and hooked by KVO).

    That's also why -setTimeValue:withTimeScale: is getting away with calling [self setMoviePosition:newMoviePosition], which doesn't match your -set_moviePosition: method. However, you should be getting a warning (and, at runtime, a crash) because you're passing a double to a setter which expects an NSNumber*.