为什么我的 KVO 依赖项在 NSArrayController 中不起作用

Why doesn't my KVO dependency not work in NSArrayController

我想将 NSArrayControllerNSTableView 结合使用以允许进行多项选择,但仅在选择单个对象时提供选定对象(而 nil 当 none 或多个被选中)。

我已尝试使用 NSArrayController 上的类别来实现此功能,如下所示:

@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

出于某种原因,当阵列控制器的选择发生变化时,selectedObject 方法不会被调用(并且其他东西正在观察 selectedObject)。这是为什么?

NSArrayControllerselection属性是奇怪的巫术。我不知道观察它的键值对(而不是 通过 的路径)是否会在选择更改时产生更改通知。毕竟,它 return 是一个代理,没有理由相信该代理的身份会随着时间而改变。

无论如何,您实际的 selectedObject 方法实际上并没有使用 selection(也不应该)。它使用 arrangedObjectsselectionIndexes。因此,您应该 return 一个包含 来自 +keyPathsForValuesAffectingSelectedObject.

的那些 键的集合

当然,如果您使用基于视图的 table,您需要确保 table 视图的 selectionIndexes 绑定绑定到数组控制器的 selectionIndexes 属性,否则数组控制器将不知道 table 视图中的选择。 (对于基于单元格的 table 视图,您通常会将列绑定到数组控制器,并且 table 视图会根据列的绑定自动绑定自己的绑定。)

最后,我认为您应该为 selectedObject 选择一个不同的名称。 Apple 很可能有一个该名称的私有方法,或者将来会添加一个。

我设法通过创建 NSArrayController 的子类并手动观察 selectionIndexes 键来实现它。我更喜欢使用类别来完成它,但这似乎有效。

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

我使用了上下文(根据 this article) to avoid removing any observers the superclass may have in dealloc (as cautioned against here)。