2

我已经设置了一个NSOutlineViewwith DataSource。

输入的数据NSOutlineView基本上是一个自定义节点树,每个节点(我们称之为这个PPDocument)具有 2 个基本属性(还有更多,但这是必不可少的部分):

  • 标签(显示的内容)
  • children(一组子节点)

当我的过滤器字段(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错误)。

有任何想法吗?您是否注意到任何明显的错误?

4

1 回答 1

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 数组所做的任何更改也需要反映在过滤后的子项中。也就是说,如果你添加一个孩子并且它通过了过滤器,你将它添加到相应位置的过滤后的孩子中。如果您移除一个孩子,它也需要从过滤的孩子数组中移除。

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

无论哪种方式,当一个节点检测到其过滤的子数组已经改变或可能已经改变(即缓存已失效)从空变为非空或反之亦然时,它需要通知其父。那是因为它的空性会影响父级是否将其保留在父级过滤的子级列表中。

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

于 2014-09-17T11:52:16.597 回答