- Published
- 5 July 2012
- Tagged
What was going to be a follow-up post got delayed, so now it's a full post of its very own.
The problem
Here is the current setup for my app's inspector panel.
In a separate window I have a graph, with the selected series "bolded" (line thickness increased). When I change the selection of the dropdown box in this panel:
- Each object in the panel should update, and
- The graph view should update
Here is how the bindings were originally setup.
This is pretty standard. But there's a problem: I have no way of getting the graph to redraw when the selection changes.
First solution
My first solution was to set up the bindings as follows.
%{ figure url="kvo.png" %}
Then, in JRWindowController
, I made methods like the following:
-(BOOL)visible {
return [selectedSeries visible];
}
-(void)setVisible:(BOOL)visible {
[selectedSeries setVisible:visible];
[graph setNeedsDisplay:YES];
}
There's a couple of problems with this code, namely:
- Duplication: If I end up with five different methods that all need to refresh the graph, I'm creating a bunch of code that does pretty much the same thing five times.
- It doesn't work: While the graph will redraw, the controls in the panel will not. I believe this is because we're hooking up the KVO to the
WindowController
's methods. For whatever particular reason, the controls on the panel won't update to reflect our selection.
Second solution
It seemed this was a perfect time to learn about KVO and how to do it manually, rather than spending this whole time using Interface Builder's GUI to do the heavy lifting. Most of it ended up being done by the following method call:
[selectedSeries addObserver:self
forKeyPath:@"visible"
options:NSKeyValueObservingOptionNew
context:NULL];
Then, later in the class definition:
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
[graphView setNeedsDisplay:YES];
}
Since there were several values I wanted to observe, I actually bundled the whole thing up in a for loop to iterate over them:
for (NSString *key in observedKeys)
[selectedSeries addObserver:self
forKeyPath:key
options:NSKeyValueObservingOptionNew
context:NULL
];
Finally, I just needed to add some code to start and stop observing series, depending on whether or not they were selected:
-(void)startObservingSeries {
for (NSString *key in observedKeys)
[selectedSeries addObserver:self
forKeyPath:key
options:NSKeyValueObservingOptionNew
context:NULL];
}
-(void)stopObservingSeries {
for (NSString *key in observedKeys)
[selectedSeries removeObserver:self forKeyPath:key];
}
-(void)setSelectedSeries:(JRGraphSeries *)newSelectedSeries {
if (selectedSeries != NULL)
[self stopObservingSeries];
selectedSeries = newSelectedSeries;
[self startObservingSeries];
[graphView setNeedsDisplay:YES];
}
Now KVO works correctly (a change to selectedSeries
triggers the right KVO methods to update panel controllers), the graph redraw works correctly (my own KVO code makes sure that happens), and even better, I've removed some code duplication. And, as a final cherry on the top, I understand not only how KVO works, but how to manually implement it in my own projects.