2014-09-17 9 views
2

Я установил NSOutlineView с помощью DataSource.Фильтрация содержимого NSOutlineView

Данные, подаваемые в NSOutlineView в основном дерево пользовательского узла, с каждым узлом (назовем это PPDocument) отличая 2 основные свойства (есть гораздо больше, но это существенная часть):

  • метка (что отображается)
  • детей (массив дочерних узлов)

Когда мой фильтр F ield (на самом деле 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

Я нашел свой ответ на ваш вопрос здесь https://github.com/johndpope/paperless – johndpope

ответ

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 предупреждает, что методы источника данных будут вызываться часто и должны быть эффективными. Например, он запрашивает количество дочерних элементов элемента и вы создаете массив фильтрованных детей, что требует создания массива фильтрованных детей этих детей и т. Д., Чтобы определить, должен ли быть ребенок, потому что он имеет дети, которые выживают при фильтрации. Затем он спрашивает, сколько детей у одного из этих детей, и вам нужно перестроить все это поддерево.

Когда я это сделал, у меня есть класс узла, который отслеживает как дочерние, так и отфильтрованные дочерние элементы в постоянных массивах. Каждый узел также должен отслеживать текущий фильтр.

Один из подходов заключается в том, чтобы держать их всегда в синхронизации. Любые изменения, внесенные в массив детей, также должны отражаться на дочерних элементах фильтрации. То есть, если вы добавите ребенка и он пройдет фильтр, вы добавите его в отфильтрованные дочерние элементы в соответствующей позиции. Если вы удалите дочерний элемент, его также необходимо удалить из фильтрованного дочернего массива.

Другим подходом является обработка фильтрованного дочернего массива в виде кеша. Любая модификация дочернего массива делает недействительным этот кеш. Каждый раз, когда запрашивается массив фильтрованных детей, он пересчитывается, если он недействителен.

В любом случае, когда узел обнаруживает, что его фильтрованный дочерний массив был изменен или может быть изменен (т. Е. Кеш был недействителен) от пустого до непустого или наоборот, ему необходимо сообщить его родительскому элементу. Это связано с тем, что его пустота влияет на то, сохраняет ли родитель его в списке фильтрованных детей родителя.

В первом подходе, где массив фильтрованных детей постоянно поддерживается, вам нужен способ установки фильтра. Это должно как обновить отфильтрованные дочерние элементы текущего узла, так и передать новый фильтр всем детям. Во втором подходе последний использованный фильтр является частью кеша. Вы проверяете, изменился ли фильтр, когда запрошен профиль фильтрованных дочерних элементов. Если это так, то это эквивалент кэша, который был признан недействительным, поэтому вы его пересчитаете.