我已经使用DataSource设置了NSOutlineView
。
提供给NSOutlineView
的数据基本上是一个自定义节点树,每个节点(让我们称之为PPDocument
)具有2个基本属性(还有更多,但是这是必不可少的部分):
当我的过滤字段(实际上NSSearchField
)发生变化时,我在大纲视图中调用reloadData
。
所以,我决定将整个过滤插入数据源,如下所示:
- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(PPDocument*)doc {
if (doc==nil) return [[[[APP documentManager] documentTree] groups] count]; // Root
else
{
if ([[[APP fileOutlineFilter] stringValue] isEqualToString:@""]) // Unfiltered
return [doc noOfChildren];
else
return [doc noOfChildrenFiltered:[[APP fileOutlineFilter] stringValue]];
}
}
- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(PPDocument*)doc {
if (doc == nil) return [[[APP documentManager] documentTree] groups][index]; // Root
else
{
if ([[[APP fileOutlineFilter] stringValue] isEqualToString:@""]) // Unfiltered
return [doc childAtIndex:index];
else
return [doc childAtIndex:index filtered:[[APP fileOutlineFilter] stringValue]];
}
}
3主要"过滤"功能:
- (NSArray*)filteredChildren:(NSString*)filter
{
NSMutableArray* ret = [[NSMutableArray alloc] initWithObjects: nil];
if (([self.label contains:filter]) && ([self.children count]==0)) return @[self];
for (PPDocument* d in _children)
{
NSArray* filtered = [d filteredChildren:filter];
if ([filtered count]>0)
{
PPDocument* newDoc = [d copy];
newDoc.children = [filtered mutableCopy];
[ret addObject:newDoc];
}
}
return ret;
}
- (NSInteger)noOfChildrenFiltered:(NSString*)filter
{
NSArray* filtered = [self filteredChildren:filter];
return [filtered count];
}
- (PPDocument*)childAtIndex:(NSInteger)index filtered:(NSString*)filter {
NSArray* filtered = [self filteredChildren:filter];
return (PPDocument*)(filtered[index]);
}
但是,它似乎没有正常工作(+ isGroupItem:
函数突然开始抛出EXC_BAD_ACCESS
错误。
有什么想法吗?你注意到有任何明显的错误吗?
答案 0 :(得分:1)
您的-filteredChildren:
方法对我来说似乎不对。
首先,它永远不应该作为它的一个孩子(过滤或不过滤)返回。它似乎也不应该制作子节点的副本。
我认为这应该有效:
- (NSArray*)filteredChildren:(NSString*)filter
{
NSIndexSet* indexes = [_children indexesOfObjectsPassingTest:BOOL ^(PPDocument* child, NSUInteger idx, BOOL *stop){
if (child.children.count)
return [[child filteredChildren:filter] count] > 0;
return [child.label contains:filter];
}];
return [_children objectsAtIndexes:indexes];
}
但是,这种方法的问题在于,您要为项目的每个查询构建过滤后的子项列表。 NSOutlineView
警告数据源方法将被频繁调用并且必须高效。例如,它询问项目的子项数,并构造过滤子项的数组,这需要构建这些子项的过滤子项的数组等,以确定是否应该存在子项,因为它具有过滤后幸存的孩子。然后它询问其中一个孩子有多少孩子,你必须重建整个子树。
当我这样做时,我的节点类跟踪持久数组中的子节点和已过滤的子节点。每个节点还必须跟踪当前过滤器。
一种方法是始终保持同步。对children数组所做的任何更改都需要反映在已过滤的子数据中。也就是说,如果添加一个子项并通过过滤器,则将其添加到相应位置的过滤子项中。如果删除子项,则也需要从过滤的子数组中删除它。
另一种方法是将过滤后的子数组视为缓存。子数组的任何修改都会使该缓存无效。每当请求过滤的子数组时,如果它无效,则重新计算。
无论哪种方式,当节点检测到其已过滤的子阵列已经改变或者可能已经改变(即,高速缓存已经无效)从空到非空或反之,它需要通知其父节点。这是因为它的空虚会影响父母是否将其保留在父母的过滤子列表中。
在第一种不断维护过滤子数组的方法中,您需要一种方法来设置过滤器。这应该既更新当前节点的过滤子节点,也将新过滤器传递给所有子节点。在第二种方法中,最后使用的过滤器是缓存的一部分。您在请求过滤的子数组时测试过滤器是否已更改。如果有,那么相当于缓存已经失效,所以你重新计算它。