En iOS, setNeedsDisplay realmente no hace llamar a drawRect … a less que la pantalla de CALayer o drawInContext finalmente llame a drawRect?

Realmente no entiendo cómo la display drawInContext y drawInContext relacionan con drawRect en la vista.

Si tengo un NSTimer que establece el [self.view setNeedsDisplay] cada 1 segundo, se llama a drawRect cada 1 segundo, como lo muestra una statement NSLog dentro de drawRect .

Pero si subclasss un CALayer y lo uso para la vista, si desactivo el método de display , ahora no se llama a drawRect . Actualización: pero la display se llama cada 1 segundo, como lo muestra una statement de NSLog.

Si drawInContext ese método de display vacío y agrego un método drawInContext vacío, nuevamente, drawRect nunca se llama. Actualización: pero drawInContext se llama cada 1 segundo, como lo muestra una instrucción NSLog.

¿Qué está sucediendo exactamente? Parece que la display puede llamar selectivamente drawInContext y drawInContext puede llamar selectivamente a drawRect (¿cómo?), Pero ¿cuál es la situación real aquí?


Actualización: hay más pistas sobre la respuesta:

Cambié el código CoolLayer.m a lo siguiente:

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

Entonces, digamos, si hay una luna (como un círculo dibujado por Core Graphics) en la location (100,100) en la Vista, y ahora la cambio a location (200,200), naturalmente, llamaré a [self.view setNeedsDisplay] , y ahora, CALayer no tendrá caching en absoluto para la nueva image de vista, ya que mi drawRect dicta cómo se mostrará ahora la luna.

Aun así, el punto de input es la display de CALayer y, a continuación, drawInContext de drawInContext : Si establezco un punto de ruptura en drawRect , la stack de llamadas muestra:

introduzca la descripción de la imagen aquí

Entonces podemos ver que la display de CoolLayer se ingresa primero, y va a la display de CALayer, y luego drawInContext de drawInContext , y luego drawInContext de drawInContext , aunque en esta situación, no existe tal caching para la nueva image.

Finalmente, el drawInContext de drawInContext llama drawInContext del delegado drawLayer:InContext . El delegado es la vista (FooView o UIView) … y drawLayer:InContext es la implementación pnetworkingeterminada en UIView (ya que no la he anulado). Por fin es que drawLayer:InContext llama drawRect .

Así que estoy adivinando dos puntos: ¿por qué se ingresa a CALayer a pesar de que no hay caching para la image? Debido a que a través de este mecanismo, la image se dibuja en el context, y finalmente vuelve a display , y la CGImage se crea a partir de este context, y luego se configura como la nueva image almacenada en caching. Así es como CALayer almacena en caching las imágenes.

Otra cosa que no estoy seguro es: si [self.view setNeedsDisplay] siempre [self.view setNeedsDisplay] drawRect para ser llamado, ¿cuándo puede usarse una image en caching en CALayer? ¿Podría ser … en Mac OS X, cuando otra window cubre una window y ahora la window superior se aleja? Ahora no necesitamos llamar a drawRect para volver a dibujar todo, pero podemos usar la image en caching en el CALayer. O en iOS, si paramos la aplicación, hacemos otra cosa y regresamos a la aplicación, entonces la image en caching puede usarse, en lugar de llamar a drawRect . ¿Pero cómo distinguir estos dos types de "sucio"? Una es una "sucia desconocida" – que la luna necesita ser networkingibujada según lo dictado por la lógica drawRect (también puede usar un número aleatorio para la coorderada). Los otros types de sucio es que estaba cubierto o hecho desaparecer, y ahora necesita ser re-mostrado.

Cuando se necesita mostrar una capa y no tiene una reserva de respaldo válida (quizás porque la capa recibió un post setNeedsDisplay ), el sistema envía el post de display a la capa.

El método -[CALayer display] se parece más o less a esto:

 - (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 shanetworking with the WindowServer process [self drawInContext:gc]; self.contents = backing; } 

Por lo tanto, si anula la display , nada de eso sucede a less que llame [super display] . Y si implementas displayLayer: en FooView , tienes que crear tu propia CGImage alguna manera y almacenarla en la propiedad de contents la capa.

El -[CALayer drawInContext:] se parece más o less a esto:

 - (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]; } } } 

La acción onDraw no está documentada por lo que sé.

El -[UIView drawLayer:inContext:] parece más o less así:

 - (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); } 

El procedimiento de actualización de UIView se basa en el estado dirty , lo que significa que es probable que la vista no se vuelva a dibujar si no hay ningún cambio en su apariencia.

Esa es la implementación interna mencionada en la reference de desarrollador.

Implementar un drawInContext o display o drawRect le dice al OS cuál desea llamar cuando la vista está sucia (needsDisplay). Elija el que desea que aparezca una vista sucia e implemente eso, y no coloque ningún código que dependa de que se ejecute en los demás.