绑定NSArrayController的NSTableView如何识别被修改的行?
问题
我将NSTableView绑定至NSArrayController,并为数组控制器管理的各数据字段注册了观察者,示例代码如下:
[_arrayController addObserver:self forKeyPath:@"arrangedObjects.userName" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil]; [_arrayController addObserver:self forKeyPath:@"arrangedObjects.enabled" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil]; [_arrayController addObserver:self forKeyPath:@"arrangedObjects.lockSettings" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
编辑表格修改数据时,观察者方法能正常被调用:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if ([keyPath compare:@"arrangedObjects.userName"] == NSOrderedSame) { // 已知userName发生变更,但无法确定是哪一个? } }
当前需要解决的问题:明确是哪个arrangedObjects触发了变更,或是对应的表格行号。由于数据对象可能多达数百个,必须定位到具体对象。(注:使用Objective-C遗留代码)
解决方案
方法1:给每个数据对象单独添加观察者
放弃监听arrangedObjects.xxx这种集合路径,改为遍历数组控制器的arrangedObjects,给每个数据对象单独加观察者监听对应属性:
// 在数组控制器加载完内容后调用(比如viewDidLoad、数据刷新后) - (void)setupObjectObservers { // 先移除旧观察者,避免重复添加导致崩溃 for (id obj in _arrayController.arrangedObjects) { [obj removeObserver:self forKeyPath:@"userName" context:nil]; [obj removeObserver:self forKeyPath:@"enabled" context:nil]; [obj removeObserver:self forKeyPath:@"lockSettings" context:nil]; } // 给每个对象添加新观察者 for (id obj in _arrayController.arrangedObjects) { [obj addObserver:self forKeyPath:@"userName" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil]; [obj addObserver:self forKeyPath:@"enabled" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil]; [obj addObserver:self forKeyPath:@"lockSettings" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil]; } } // 观察者方法中直接获取变更对象和行号 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if ([keyPath isEqualToString:@"userName"]) { // object就是发生变更的数据对象 NSInteger rowIndex = [_arrayController.arrangedObjects indexOfObject:object]; // rowIndex就是对应的表格行号(数组顺序和表格一致时有效) } else if ([keyPath isEqualToString:@"enabled"]) { NSInteger rowIndex = [_arrayController.arrangedObjects indexOfObject:object]; // 处理enabled字段变更 } else if ([keyPath isEqualToString:@"lockSettings"]) { NSInteger rowIndex = [_arrayController.arrangedObjects indexOfObject:object]; // 处理lockSettings字段变更 } }
注意:当数组控制器内容增删时(比如添加新对象、删除旧对象),要重新调用setupObjectObservers,防止内存泄漏或遗漏监听。
方法2:利用KVO集合变更的内置信息
监听arrangedObjects的集合属性变更时,KVO的change字典里会包含NSKeyValueChangeObjectsKey,这个数组存的就是发生变更的对象:
// 保留原有的观察者注册方式即可
在观察者方法里提取变更对象:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if ([keyPath isEqualToString:@"arrangedObjects.userName"]) { NSArray *changedObjects = change[NSKeyValueChangeObjectsKey]; if (changedObjects.count > 0) { id targetObj = changedObjects.firstObject; NSInteger rowIndex = [_arrayController.arrangedObjects indexOfObject:targetObj]; // 定位到具体对象和行号,处理逻辑 } } else if ([keyPath isEqualToString:@"arrangedObjects.enabled"]) { NSArray *changedObjects = change[NSKeyValueChangeObjectsKey]; if (changedObjects.count > 0) { id targetObj = changedObjects.firstObject; NSInteger rowIndex = [_arrayController.arrangedObjects indexOfObject:targetObj]; } } else if ([keyPath isEqualToString:@"arrangedObjects.lockSettings"]) { NSArray *changedObjects = change[NSKeyValueChangeObjectsKey]; if (changedObjects.count > 0) { id targetObj = changedObjects.firstObject; NSInteger rowIndex = [_arrayController.arrangedObjects indexOfObject:targetObj]; } } }
注意:这种方式要求数据对象本身符合KVO规范(比如自定义模型的属性是动态的,或者用@synthesize正确生成)。
方法3:直接用NSTableView代理方法
既然变更是表格编辑触发的,直接用NSTableViewDelegate的代理方法更直接,不需要KVO:
// 实现表格编辑结束的代理方法 - (void)tableView:(NSTableView *)tableView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row { id dataObj = _arrayController.arrangedObjects[row]; NSString *colID = tableColumn.identifier; if ([colID isEqualToString:@"userNameColumn"]) { // 明确知道是第row行的userName字段变更,dataObj就是对应的数据对象 } else if ([colID isEqualToString:@"enabledColumn"]) { // 处理enabled字段变更 } else if ([colID isEqualToString:@"lockSettingsColumn"]) { // 处理lockSettings字段变更 } }
这种方式适合纯表格编辑触发的场景,代码更简洁,不需要维护额外的观察者。
内容的提问来源于stack exchange,提问作者Chuck D
相关产品推荐
相关产品推荐

