Могу ли я использовать CALayer для ускорения рендеринга представления?

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

Как я могу это сделать? Я не нашел много хороших руководств по такого рода вещам, и ни одно из них не говорит о рендеринге NSBezierPaths на CALayer. Идеи кто-нибудь?


person mtmurdock    schedule 21.02.2012    source источник


Ответы (1)


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

Все, что вам нужно сделать, это добавить CALayer объектов для каждого элемента контента в вашем представлении. Чтобы рисовать слои, вы должны установить свое представление в качестве делегата для каждого слоя, а затем реализовать метод drawLayer:inContext:.

В этом методе вы просто рисуете содержимое каждого слоя:

- (void)drawLayer:(CALayer*)layer inContext:(CGContextRef)ctx
{
    if(layer == yourBackgroundLayer)
    {   
        //draw your background content in the context
        //you can either use Quartz drawing directly in the CGContextRef,
        //or if you want to use the Cocoa drawing objects you can do this:
        NSGraphicsContext* drawingContext = [NSGraphicsContext graphicsContextWithGraphicsPort:ctx flipped:YES];
        NSGraphicsContext* previousContext = [NSGraphicsContext currentContext];
        [NSGraphicsContext setCurrentContext:drawingContext];
        [NSGraphicsContext saveGraphicsState];
        //draw some stuff with NSBezierPath etc
        [NSGraphicsContext restoreGraphicsState];
        [NSGraphicsContext setCurrentContext:previousContext];
    }
    else if (layer == someOtherLayer)
    {
        //draw other layer
    }
    //etc etc
}

Если вы хотите обновить содержимое одного из слоев, просто вызовите [yourLayer setNeedsDisplay]. Затем это вызовет метод делегата выше, чтобы предоставить обновленное содержимое слоя.

Обратите внимание, что по умолчанию, когда вы меняете содержимое слоя, Core Animation обеспечивает хороший плавный переход для нового содержимого. Однако, если вы обрабатываете рисунок самостоятельно, вам, вероятно, это не нужно, поэтому, чтобы предотвратить анимацию постепенного исчезновения по умолчанию при изменении содержимого слоя, вам также необходимо реализовать метод делегата actionForLayer:forKey: и предотвратить анимацию, возвращая нулевое действие:

- (id<CAAction>)actionForLayer:(CALayer*)layer forKey:(NSString*)key 
{
    if(layer == someLayer)
    {
        //we don't want to animate new content in and out
        if([key isEqualToString:@"contents"])
        {
            return (id<CAAction>)[NSNull null];
        }
    }
    //the default action for everything else
    return nil;
}
person Rob Keniger    schedule 21.02.2012
comment
Спасибо, это очень полезно. Однако у меня возникли проблемы с запуском метода делегата. Я устанавливаю свое представление в качестве делегата и добавляю слои в качестве подслоев корневого слоя, и я вызываю setNeedsDisplay, но drawLayer:inContext никогда не вызывается. Любые идеи? - person mtmurdock; 21.02.2012
comment
Я также переопределяю drawRect:, может ли это быть причиной того, что он не вызывает другую функцию? - person mtmurdock; 24.02.2012
comment
Ага. Вы не можете иметь одновременно drawRect: и чертеж на основе слоев на виде. Вы также должны убедиться, что вы вызвали setWantsLayer:YES в представлении, иначе слои будут игнорироваться. - person Rob Keniger; 24.02.2012
comment
когда ты звонишь setWantsLayer:? только один раз в конструкторе? Или каждый раз, когда вы рисуете? - person mtmurdock; 24.02.2012
comment
Хорошо, у меня это отображается сейчас. Мне пришлось установить положение и границы, чтобы он отображался, но это не рендеринг, где я ожидал. Все выпадает из поля зрения в правом нижнем углу. Используют ли CALayers систему координат, отличную от представлений? Являются ли позиции относительно собственного вида или окна? - person mtmurdock; 24.02.2012
comment
Если представление настроено в Interface Builder, вы можете включить его в инспекторе основной анимации для представления (установите флажок рядом с представлением в разделе «Основной слой анимации»). В противном случае вы можете включить его в методе initWithFrame: (если вы создаете слой программно) или в initWithCoder:, если вы загружаете представление из пера. - person Rob Keniger; 24.02.2012
comment
Я не понимаю. Что я включаю и что это делает? - person mtmurdock; 24.02.2012
comment
CALayers используют другой способ указания координат. Взгляните на документы - person Rob Keniger; 24.02.2012
comment
Я имел в виду setWantsLayer. - person Rob Keniger; 24.02.2012