过滤NSOutlineView的内容

时间:2014-09-17 08:48:01

标签: objective-c macos cocoa nsoutlineview nssearchfield

我已经使用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错误。

有什么想法吗?你注意到有任何明显的错误吗?

1 个答案:

答案 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数组所做的任何更改都需要反映在已过滤的子数据中。也就是说,如果添加一个子项并通过过滤器,则将其添加到相应位置的过滤子项中。如果删除子项,则也需要从过滤的子数组中删除它。

另一种方法是将过滤后的子数组视为缓存。子数组的任何修改都会使该缓存无效。每当请求过滤的子数组时,如果它无效,则重新计算。

无论哪种方式,当节点检测到其已过滤的子阵列已经改变或者可能已经改变(即,高速缓存已经无效)从空到非空或反之,它需要通知其父节点。这是因为它的空虚会影响父母是否将其保留在父母的过滤子列表中。

在第一种不断维护过滤子数组的方法中,您需要一种方法来设置过滤器。这应该既更新当前节点的过滤子节点,也将新过滤器传递给所有子节点。在第二种方法中,最后使用的过滤器是缓存的一部分。您在请求过滤的子数组时测试过滤器是否已更改。如果有,那么相当于缓存已经失效,所以你重新计算它。

相关问题