I would like to use an NSArrayController
with an NSTableView
to allow multiple selection but only provided a selected object when a single object is selected (and nil
when none or multiple are selected).
I've attempted to implement this with a category on NSArrayController
, as shown here:
@implementation NSArrayController (SelectedObject)
+ (NSSet *)keyPathsForValuesAffectingSelectedObject {
return [NSSet setWithObject:@"selection"];
}
- (id)selectedObject {
// Get the actual selected object (or nil) instead of a proxy.
if (self.selectionIndexes.count == 1) {
return [self arrangedObjects][self.selectionIndex];
}
return nil;
}
@end
For some reason, the selectedObject
method is not called when the selection of the array controller changes (and something else is observing selectedObject
). Why is this?
I managed to get this working by creating a subclass of NSArrayController
and manually observing the selectionIndexes
key. I'd prefer to do it using a category but this does appear to work.
static NSString *const kObservingSelectionIndexesContext = @"ObservingSelectionIndexesContext";
@implementation BetterArrayController
- (void)awakeFromNib {
[super awakeFromNib];
[self addObserver:self forKeyPath:@"selectionIndexes" options:NSKeyValueObservingOptionInitial|NSKeyValueObservingOptionNew context:(void *)&kObservingSelectionIndexesContext];
}
- (void)dealloc {
[self removeObserver:self forKeyPath:@"selectionIndexes" context:(void *)&kObservingSelectionIndexesContext];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (context == (void *)&kObservingSelectionIndexesContext) {
[self willChangeValueForKey:@"selectedObject"];
[self didChangeValueForKey:@"selectedObject"];
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
- (id)selectedObject {
// Get the actual selected object (or nil) instead of a proxy.
if (self.selectionIndexes.count == 1) {
return [self arrangedObjects][self.selectionIndex];
}
return nil;
}
@end
I used a context (as per this article) to avoid removing any observers the superclass may have in dealloc
(as cautioned against here).