Here's the setup: I have a subclass of IKImageBrowserView whose zoomValue
property is bound to a key VFBrowserZoomValue
in the shared NSUserDefaultsController
. I have an NSSlider
whose value
binding is bound to the same key.
This works perfectly to change the zoomValue
of the browser from the slider.
I'm trying to implement -magnifyWithEvent:
in my IKImageBrowserView
subclass to allow zooming the browser with the pinch gesture on trackpads.
Here's my implementation:
-(void)magnifyWithEvent:(NSEvent *)event
{
if ([event magnification] > 0) {
if ([self zoomValue] < 1) {
[self setZoomValue: [self zoomValue] + [event magnification]];
}
}
else if ([event magnification] < 0) {
if ([self zoomValue] + [event magnification] > 0.1) {
[self setZoomValue: [self zoomValue] + [event magnification]];
}
else {
[self setZoomValue: 0.1];
}
}
}
This changes the browser's zoomValue
correctly. The problem is that the NSUserDefaults
is not updated with the new value.
Elsewhere in the app, I have an implementation of -observeValueForKeyPath:ofObject:change:context:
that observes the browser's zoomValue
. If I log the values of the browser's zoom, the slider's value and the key in defaults in that method, I see that the browser's zoomValue has not been pushed into NSUserDefaults and the slider hasn't updated.
I've tried surrounding the -magnifyWithEvent:
method with calls to -{will,did}ChangeValueForKey
to no effect.
The KVO flow for bindings isn't orthogonal; a binding isn't a property, it's a reference to a property. This is the shorthand to remember for how bindings work:
Thus when a view with bindings handles events, it needs to propagate changes to the properties its bindings reference itself.
Here's what your code might look like, with a utility method for doing the heavy lifting of propagating changes through bindings:
- (void)magnifyWithEvent:(NSEvent *)event
{
if ([event magnification] > 0) {
if ([self zoomValue] < 1) {
[self setZoomValue: [self zoomValue] + [event magnification]];
}
}
else if ([event magnification] < 0) {
if ([self zoomValue] + [event magnification] > 0.1) {
[self setZoomValue: [self zoomValue] + [event magnification]];
}
else {
[self setZoomValue: 0.1];
}
}
// Update whatever is bound to our zoom value.
[self updateValue:[NSNumber numberWithFloat:[self zoomValue]]
forBinding:@"zoomValue"];
}
It's a little unfortunate that ImageKit requires the use of @"zoomValue"
to reference the Zoom Value binding of an IKImageBrowserView, most bindings in AppKit have their own global string constant like NSContentBinding
.
And here's that generic utility method to propagate the change through the binding:
- (void)updateValue:(id)value forBinding:(NSString *)binding
{
NSDictionary *bindingInfo = [self infoForBinding:binding];
if (bindingInfo) {
NSObject *object = [bindingInfo objectForKey:NSObservedObjectKey];
NSString *keyPath = [bindingInfo objectForKey:NSObservedKeyPathKey];
NSDictionary *options = [bindingInfo objectForKey:NSOptionsKey];
// Use options to apply value transformer, placeholder, etc. to value
id transformedValue = value; // exercise for the reader
// Tell the model or controller object the new value
[object setValue:transformedValue forKeyPath:keyPath];
}
}
Actually applying placeholders, value transformers, and the like is left as an exercise for the reader.