2012-05-28 2 views
3

Я не совсем понимаю, как CALayer display и drawInContext относятся к drawRect на вид.На iOS, setNeedsDisplay действительно не вызывает drawRect для вызова ... если показ CALayer или drawInContext окончательно не вызовет drawRect?

Если у меня есть NSTimer, который устанавливает [self.view setNeedsDisplay] каждые 1 секунду, а затем drawRect называют каждую 1 секунду, как показан оператором NSLog внутри drawRect.

Но если я подклассию CALayer и использую это для представления, если я делаю метод display пустым, то теперь drawRect никогда не вызывается. Обновление: Но display вызывается каждые 1 секунду, как показано в инструкции NSLog.

Если я удалю этот пустой метод display и добавлю пустой метод drawInContext, снова drawRect никогда не вызывается. Обновление: Но drawInContext вызывается каждые 1 секунду, как показано в инструкции NSLog.

Что именно происходит? Кажется, что display может выборочно позвонить drawInContext и drawInContext может выборочно каким-то образом позвонить drawRect (как?), Но какова реальная ситуация здесь?


Update: есть более ключ к ответу:

Я изменил CoolLayer.m код на следующее:

-(void) display { 
    NSLog(@"In CoolLayer's display method"); 
    [super display]; 
} 

-(void) drawInContext:(CGContextRef)ctx { 
    NSLog(@"In CoolLayer's drawInContext method"); 
    [super drawInContext:ctx]; 
} 

Так, скажем, если есть луна (как круг, нарисованный Core Graphics) в местоположении (100,100) в представлении, и теперь я меняю его на местоположение (200,200), естественно, я вызову [self.view setNeedsDisplay], и теперь у CALayer не будет никакого кэша для нового изображения вида , Как мой drawRect диктует, как теперь должна отображаться Луна.

Тем не менее, точка входа CALayer-х display, а затем CALayer-х drawInContext: Если установить точку останова на drawRect, стек вызовов показывает:

enter image description here

Таким образом, мы можем видеть, что CoolLayer-х display является вошел первым, и он переходит в CALayer display, а затем в CoolLayer drawInContext, а затем в CALayer drawInContext, хотя в этой ситуации такой кеш для нового изображения не существует.

Затем, наконец, drawInContext CALayer вызывает делегат drawLayer:InContext. Делегат - это представление (FooView или UIView) ... и drawLayer:InContext - это реализация по умолчанию в UIView (поскольку я не переопределял ее). Наконец, drawLayer:InContext звонит drawRect.

Итак, я угадываю два момента: почему он входит в CALayer, хотя для изображения нет кеша? Потому что благодаря этому механизму изображение рисуется в контексте и, наконец, возвращается к display, а CGImage создается из этого контекста, а затем он теперь устанавливается как новое кэшированное изображение. Так CALayer кэширует изображения.

Другое дело, что я не совсем уверен: если [self.view setNeedsDisplay] всегда вызывает drawRect, то когда можно использовать кешированное изображение в CALayer? Может быть ... в Mac OS X, когда другое окно закрывает окно, и теперь верхнее окно удаляется. Теперь нам не нужно звонить drawRect, чтобы перерисовать все, но можно использовать кешированное изображение в CALayer. Или в iOS, если мы остановим приложение, сделаем что-то еще и вернемся к приложению, тогда можно использовать кэшированное изображение вместо того, чтобы звонить drawRect. Но как отличить эти два типа «грязных»? Один из них - «неизвестный грязный» - что луну нужно перерисовать, как это продиктовано логикой drawRect (она может также использовать случайное число для этой координаты). Другие типы грязных - это то, что они были закрыты или исчезли, и теперь их нужно повторить.

ответ

1

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

Это внутренняя реализация, упомянутая в ссылке разработчика.

+0

Как работает CALayer? –

+0

В документации явно указано, что 'setNeedsDisplay' работает только для графиков UIKit и Core Graphics, я считаю, что обновление для грязных просмотров действительно для обоих. Помните, что это недопустимо для 'CAEAGLLayer', как указано в документации. –

+0

вопрос в том, что мы используем 'setNeedsDisplay', но переопределяем метод CALayer, тогда' drawRect' не вызывается и почему? –

12

Если слой необходимо отобразить и не имеет действительного хранилища (возможно, потому, что слой получил сообщение setNeedsDisplay), система отправляет сообщение display на уровень.

Метод -[CALayer display] выглядит примерно так:

- (void)display { 
    if ([self.delegate respondsToSelector:@selector(displayLayer:)]) { 
     [[self.delegate retain] displayLayer:self]; 
     [self.delegate release]; 
     return; 
    } 

    CABackingStoreRef backing = _backingStore; 
    if (!backing) { 
     backing = _backingStore = ... code here to create and configure 
      the CABackingStore properly, given the layer size, isOpaque, 
      contentScale, etc. 
    } 

    CGContextRef gc = ... code here to create a CGContext that draws into backing, 
     with the proper clip region 
    ... also code to set up a bitmap in memory shared with the WindowServer process 

    [self drawInContext:gc]; 
    self.contents = backing; 
} 

Итак, если вы переопределить display, никто из этого не произойдет, если вы звоните [super display]. И если вы реализуете displayLayer: в FooView, вы должны создать свой собственный CGImage как-нибудь и сохранить его в свойстве contents слоя.

Метод -[CALayer drawInContext:] выглядит примерно так:

- (void)drawInContext:(CGContextRef)gc { 
    if ([self.delegate respondsToSelector:@selector(drawLayer:inContext:)]) { 
     [[self.delegate retain] drawLayer:self inContext:gc]; 
     [self.delegate release]; 
     return; 
    } else { 
     CAAction *action = [self actionForKey:@"onDraw"]; 
     if (action) { 
      NSDictionary *args = [NSDictionary dictionaryWithObject:gc forKey:@"context"]; 
      [action runActionForKey:@"onDraw" object:self arguments:args]; 
     } 
    } 
} 

onDraw действие не документировано, насколько я знаю.

Метод -[UIView drawLayer:inContext:] выглядит примерно так:

- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)gc { 
    set gc's stroke and fill color spaces to device RGB; 
    UIGraphicsPushContext(gc); 
    fill gc with the view's background color; 
    if ([self respondsToSelector:@selector(drawRect:)]) { 
     [self drawRect:CGContextGetClipBoundingBox(gc)]; 
    } 
    UIGraphicsPopContext(gc); 
} 
+0

, кстати, откуда вы знаете? вы читаете исходный код или просто угадываете? –

+4

Я использовал Хоппер, чтобы разобрать библиотеки симуляторов IOS. –

0

Реализация drawInContext или дисплея или DrawRect говорит OS, который один вы хотите назвать, когда вид загрязнен (needsDisplay). Выберите тот, который вы хотите вызвать для грязного представления, и реализуйте его, и не ставьте какой-либо код, который вы зависнете от того, чтобы выполняться в других.