NSOutlineView подпрыгивает вверх, когда обновления контента

У меня есть NSOutlineView, который отображает иерархию каталогов. Он связан с NSTreeController, который связан с моим классом, который управляет узлами файловой системы. Когда происходит событие файловой системы, я запускаю уведомление KVO по пути children, что приводит к обновлению плана. Но когда он обновляется, он неожиданно прокручивается до самого верха. Я хочу, чтобы положение прокрутки оставалось неизменным. Любые идеи?

Здесь код, который запускается при возникновении события FS:

- (void)URLWatcher:(CDEvents *)URLWatcher eventOccurred:(CDEvent *)event {
    [self willChangeValueForKey:@"children"];
    children = nil; // this will refreshed next time children is called
    [self didChangeValueForKey:@"children"];
}

Это в модели, поэтому я не могу получить доступ к представлению.

Ответы

Ответ 1

Я не тестировал и не пытался сделать следующее, но думал, что я все равно сделаю это.

Во-первых, управлять чем-либо сложным с помощью NSTableView или NSOutlineView с помощью NS * Controller является болезненным и жертвует точным контролем в обмен на простоту. Если вы обнаружите, что пытаетесь вести боевое поведение в этой ситуации, подумайте о том, чтобы реализовать собственный источник и делегировать протоколы (NSTableViewDataSource, NSTableViewDelegate или NSOutlineViewDataSource, NSOutlineViewDelegate) в своем собственном контроллере.

Во-вторых, комментарий Уоррена Бертона относительно увольнения уведомлений KVO уместен, так как вы должны сообщать ответственному диспетчеру (ваш NSTreeController) об изменениях, поскольку он контролирует (и наблюдает) эту коллекцию в любом случае. Более того, вы должны напрямую использовать методы NSTreeController add/insert/remove. То, как вы сейчас это делаете (ударяя всю структуру каждый раз, когда вы сбрасываете значение, затем reset позже), приведет к перезагрузке всего дерева. Поскольку контроллер наблюдает за этой коллекцией, он сообщает обрисованному контуру, чтобы обновить себя, возможно, чтобы сначала увидеть пустой контур, затем более расширенную версию контура, которая потеряет ваши пользовательские состояния расширения и т.д. Изменение модель через контроллер дерева позволит более разумные, более эффективные обновления вида.

В-третьих, вы могли бы рассмотреть возможность использования моего второго пункта выше, подклассифицируя NSTreeController и переопределяя методы add/insert/remove, чтобы сделать следующее:

  • Запросить представление схемы для -visibleRect.
  • Вызовите super, чтобы начать изменение.
  • Расскажите очертите схему -scrollRectToVisible:.

Возможно, вам придется задержать вызов на шаге 3, запланировав его в основном потоке (так происходит после текущей поездки через цикл запуска). Или, поочередно, замените шаг 3 с сохранением видимого прямоугольника где-нибудь и реализуя методы NSOutlineViewDelegate -outlineView:didAdd/RemoveRowView:forRow:, чтобы проверить этот флаг THEN называть -scrollRectToVisible: оттуда, если rect не равен нулю (запомните reset его NSZeroRect, чтобы он не пытается настраивать прокрутку каждый раз, когда строка контура добавляется или удаляется).

Clunky, но разумный (?) путь, который позволяет вам сохранять NSTreeController.

В-четвертых, в качестве альтернативы (и как я пойду) вы можете полностью удалить NSTreeController и реализовать протоколы NSOutlineViewDataSource (и NSOutlineViewDelegate) в своем собственном классе контроллера и позволить этому контроллеру напрямую обрабатывать добавление или удаление из вашей древовидной структуры. Затем он становится более чистым, так как вам не нужно беспокоиться о тайм-аутах KVO. При любом добавлении узлов вы можете заметить видимый прямоугольник, обновить представление схемы, а затем отрегулировать прокрутку в пределах одного и того же метода и пропустить цикл выполнения.

Я надеюсь, что это поможет и не слишком бессвязно.: -)

Ответ 2

Я могу посоветовать вам сохранить текущее смещение вида прокрутки и восстановить его после отправки уведомления о KVO и обновления плана.

- (void)updateOutlineView:(NSOutlineView *)outlineView
{
  // first save offset
  NSScrollView *scrollView = [outlineView enclosingScrollView];
  NSClipView *clipView = [scrollView contentView];
  NSPoint offset = clipView.bounds.origin;
  // send KVO notification of the 'children' keypath
  // ...
  // restore offset
  [clipView scrollPoint:offset];
}

Посмотрите, что точка прокрутки является абсолютной величиной. Вы можете рассчитать точку назначения в соответствии с обновленной высотой обзора.

//... before the notification sent
CGFloat height = [[[[scrollView documentView] frame] size] height];
CGFloat yValue = offset.y / height;
//... after the outline view updated
CGFloat newHeight = [[[[scrollView documentView] frame] size] height];
offset.y = newHeight * yValue;
[clipView scrollPoint:offset];