Слой, содержащий NSView в NSOutlineView

Я пытаюсь создать пользовательский NSView, на котором размещена иерархия CALayer для эффективного отображения. Этот NSView затем внедряется внутри NSTableCellView, который отображается с помощью NSOutlineView на основе View.

Проблема заключается в том, что всякий раз, когда я расширяю или сворачиваю элемент, все строки перемещаются, но содержимое слоя остается отображаемым в позиции, которая была перед изменением контура.

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

Я отлаживал это поведение с помощью инструментов, и кажется, что прокрутка провоцирует операцию компоновки, которая обновляет слои вызовом setPosition:, который должен был произойти при расширении или свертывании элементов.

Вот пример кода для простого уровня размещения подкласса NSView.

@interface TestView : NSView

@end

@implementation TestView

- (instancetype)initWithFrame:(NSRect)frameRect
{
    self = [super initWithFrame:frameRect];
    CAShapeLayer* layer = [CAShapeLayer layer];
    layer.bounds = self.bounds;
    layer.position = CGPointMake(NSMidX(self.bounds), NSMidY(self.bounds));
    layer.path = [NSBezierPath bezierPathWithOvalInRect:self.bounds].CGPath;
    layer.fillColor = [NSColor redColor].CGColor;
    layer.delegate = self;
    self.layer = layer;
    self.wantsLayer = YES;
    return self;
}

@end

Я пробовал много потенциальных решений этой проблемы, но не смог найти какой-либо интересный метод, вызываемый в экземпляре NSView, который можно было бы переопределить для вызова [self.layer setNeedsDisplay] или [self.layer setNeedsLayout]. Я также пробовал различные настройки для самого CALayer, например:

layer.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;
layer.needsDisplayOnBoundsChange = YES;
self.layerContentsRedrawPolicy = NSViewLayerContentsRedrawOnSetNeedsDisplay;

Может ли кто-нибудь помочь мне разобраться, как правильно отобразить этот слой внутри NSOutlineView?

Ответы

Ответ 1

В итоге я ответил на свой вопрос. Проблема была не в том, как мой TestView был реализован. Я просто пропустил один из шагов для поддержки поддержки CoreAnimation в приложении. Соответствующая ссылка находится в руководстве по программированию основной анимации.

В принципе, в iOS Core Animation и поддержка слоев всегда включена по умолчанию. В OS X он должен быть включен следующим образом:

  • Ссылка на структуру QuartzCore
  • Включить поддержку слоя для одного или нескольких объектов NSView, выполнив одно из следующих действий:
    • В ваших файлах nib используйте инспектор View Effects, чтобы включить поддержку слоев для ваших просмотров. Инспектор отображает флажки для выбранного вида и его подпунктов. Рекомендуется, чтобы вы включили поддержку слоев в представлении содержимого вашего окна, когда это возможно.
    • Для представлений, которые вы создаете программно, вызовите метод setWantsLayer: и передайте значение YES, чтобы указать, что представление должно использовать слои.

Как только я включаю поддержку слоя для любого из родителей NSOutlineView, различные глюки решаются.

Ответ 2

Трудно прочитать справочные документы NSOutlineView и найти информацию о повторном использовании ячеек, которая, вероятно, даст вам возможность приступить.

Возможно, вы посмотрели на outlineViewItemDidCollapse:, но это бесполезно для нашей проблемы, потому что у него нет указателя на NSView, и это потому, что оно больше, чем представления на основе представления.

Возможно, одно полезное упоминание, закодированное в протоколе NSOutlineViewDelegate, в разделе по методам NSOutlineView, основанным на представлении, есть одно упоминание внутри outlineView:didRemoveRowView:forRow:, что:

Удаленный rowView может быть повторно использован таблицей, поэтому любые дополнительные вставленные представления должны быть удалены в этот момент.

Другими словами, когда вы вызываете представление схемы makeViewWithIdentifier:owner:, для cellView или rowView с определенным ID вы часто получаете переработанное представление. Особенно часто из-за краха. Кстати, этот метод из суперкласса NSTableView, и в этой ссылке также этот комментарий:

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

Таким образом, у вас есть возможность изменить иерархию представлений или свойства niling в didRemoveRowView:forRow. Тем не менее, похоронен в третьей ссылке cocoa, что для NSView в комментарии к prepareForReuse, этот комментарий:

Этот метод предлагает способ для reset просмотра некоторого начального состояния, чтобы его можно было повторно использовать. Например, класс NSTableView использует его для подготовки просмотров для повторного использования и тем самым позволяет избежать затрат на создание новых представлений при прокрутке их в виде. Если вы внедряете систему повторного использования в свой собственный код, вы можете вызвать этот метод из своего собственного кода до повторного использования.

Итак, TL; DR, вам нужно реализовать prepareForReuse.

Уместными ссылками являются (в основном) суперклассы как NSOutlineView, так и NSTableCellView.

И, FWIW, здесь был подобный вопрос, где, похоже, вопросник говорит о том, что ситуация еще хуже, чем я думаю, в том, что NSOutlineView более творческий позади сцены, чем NSTableView.

В моей собственной работе с контурными представлениями и встроенными NSTextViews я видел ужасно ужасные икоты рендеринга, связанные с расширением/сбой/прокруткой, которые, как мне кажется, управляются только методами NSOutlineViewDelegate. В iOS они сделали все, чтобы переименовать makeViewWithIdentifier в более явный dequeueReusableCellViewWithIdentifier.

Ответ 3

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

По моему опыту, слой, сразу назначенный для представления (в отличие от подслоев), не нуждается в настройках маски, рамки или авторезиста. Он автоматически делается для отслеживания границ представления. На самом деле, я бы не стал устанавливать эти свойства, на всякий случай, что прерывает автоматическую синхронизацию с ограничением вида rect.

Итак, возникает вопрос: как вы настраиваете представление для перемещения или изменения размера с помощью своего супервизора? Вы используете автоматическую компоновку? Если это так, отключили ли вы его translatesAutoresizingMaskIntoConstraints? Если да для обоих, какие ограничения вы задаете в представлении? Если это не так, как вы позиционировали представление в своем супервизии? Какую рамку вы установили? Кроме того, супервизор сконфигурирован для автоматического анализа своих подзапросов (возможно, да, поскольку это значение по умолчанию)? Каково ваше мнение autoresizingMask?

Вы также можете переопределить -setFrameOrigin: и -setFrameSize: в своем классе пользовательского вида и вызвать супер. Кроме того, добавьте ведение журнала, чтобы показать, когда это произойдет, и что такое новый прямоугольник. Является ли ваше представление перемещенным, как вы ожидаете, когда вы расширяете или сворачиваете строки?