iphone ios delegates core-animation

iphone - iOS: utilizando ''drawRect:'' de UIView frente a su delegado ''drawLayer: inContext:''



delegates core-animation (6)

Tengo una clase que es una subclase de UIView . Puedo dibujar cosas dentro de la vista implementando el método drawRect o implementando drawLayer:inContext: que es un método delegado de CALayer .

Tengo dos preguntas:

  1. ¿Cómo decidir qué enfoque usar? ¿Hay un caso de uso para cada uno?
  2. Si implemento drawLayer:inContext: se llama (y drawRect no lo es, al menos en lo que se refiere a poner un punto de interrupción), incluso si no asigno mi vista como el delegado CALayer usando:

    [[self layer] setDelegate:self];

    ¿cómo se llama al método delegado si mi instancia no está definida para ser delegado de la capa? y qué mecanismo impide llamar a drawRect si se llama a drawLayer:inContext: :?


¿Cómo decidir qué enfoque usar? ¿Hay un caso de uso para cada uno?

Siempre use drawRect: y nunca use un UIView como el delegado de dibujo para cualquier CALayer .

¿cómo se llama al método delegado si mi instancia no está definida para ser delegado de la capa? y qué mecanismo impide llamar a drawRect si se llama a drawLayer:inContext: :?

Cada instancia de UIView es el delegado de dibujo para su respaldo CALayer . Es por eso que [[self layer] setDelegate:self]; parecía no hacer nada. Es redundante El método drawRect: es efectivamente el método delegado de dibujo para la capa de la vista. Internamente, UIView implementa drawLayer:inContext: donde hace algunas cosas propias y luego llama a drawRect: Puedes verlo en el depurador:

Es por esto que drawRect: nunca se llamó cuando implementó drawLayer:inContext: También es la razón por la que nunca debe implementar ninguno de los métodos de delegación de dibujo CALayer en una subclase UIView personalizada. Tampoco debe hacer ninguna vista del delegado de dibujo para otra capa. Eso causará todo tipo de extravagancia.

Si está implementando drawLayer:inContext: porque necesita acceder a CGContextRef , puede obtenerlo desde el interior de su drawRect: llamando a UIGraphicsGetCurrentContext() .


Aquí están los códigos de Sample ZoomingPDFViewer de Apple:

-(void)drawRect:(CGRect)r { // UIView uses the existence of -drawRect: to determine if it should allow its CALayer // to be invalidated, which would then lead to the layer creating a backing store and // -drawLayer:inContext: being called. // By implementing an empty -drawRect: method, we allow UIKit to continue to implement // this logic, while doing our real drawing work inside of -drawLayer:inContext: } -(void)drawLayer:(CALayer*)layer inContext:(CGContextRef)context { ... }


En iOS, la superposición entre una vista y su capa es muy grande. Por defecto, la vista es el delegado de su capa e implementa el método drawLayer:inContext: la capa. Según tengo entendido, drawRect: y drawLayer:inContext: son más o menos equivalentes en este caso. Posiblemente, la implementación predeterminada de drawLayer:inContext: calls drawRect: o drawRect: solo se drawLayer:inContext: si drawLayer:inContext: no está implementado por su subclase.

¿Cómo decidir qué enfoque usar? ¿Hay un caso de uso para cada uno?

Realmente no importa. Para seguir la convención, normalmente drawRect: y reservaré el uso de drawLayer:inContext: cuando de hecho tenga que dibujar subcapas personalizadas que no sean parte de una vista.


La documentación de Apple dice lo siguiente: "También hay otras maneras de proporcionar el contenido de una vista, como establecer directamente el contenido de la capa subyacente, pero anulando el método drawRect: es la técnica más común".

Pero no entra en detalles, por lo que debería ser una pista: no lo hagas a menos que realmente quieras ensuciarte las manos.

El delegado de la capa de UIView apunta a UIView. Sin embargo, UIView se comporta de manera diferente dependiendo de si se implementa o no drawRect: Por ejemplo, si establece las propiedades en la capa directamente (como su color de fondo o su radio de esquina), estos valores se sobrescriben si tiene un método drawRect: incluso si está completamente vacío (es decir, no llama incluso a super).


drawRect solo debe implementarse cuando sea absolutamente necesario. La implementación predeterminada de drawRect incluye una serie de optimizaciones inteligentes, como el almacenamiento en caché inteligente de la representación de la vista. Al anular, elude todas esas optimizaciones. Eso es malo. El uso eficaz de los métodos de diseño de capas casi siempre superará a un drawRect personalizado. Apple usa un UIView como delegado para un CALayer menudo, de hecho, cada UIView es el delegado de su capa . Puede ver cómo personalizar el dibujo de capa dentro de una UIView en varias muestras de Apple, incluido (en este momento) ZoomingPDFViewer.

Si bien el uso de drawRect es común, es una práctica que se ha desaconsejado desde al menos 2002/2003, IIRC. No quedan muchas buenas razones para seguir ese camino.

Optimización de rendimiento avanzada en iPhone OS (diapositiva 15)

Fundamentos básicos de animación

Comprender la representación de UIKit

Preguntas y respuestas técnicas QA1708: Mejora del rendimiento del dibujo de imagen en iOS

Ver la guía de programación: Optimizar el dibujo de la vista


Si usa drawLayer(_:inContext:) o drawRect(_:) (o ambos) para el código de dibujo personalizado depende de si necesita acceder al valor actual de una propiedad de capa mientras se está animando.

Estuve luchando hoy con varios problemas de representación relacionados con estas dos funciones al implementar mi propia clase Label . Después de revisar la documentación, hacer un poco de prueba y error, descompilar UIKit e inspeccionar el ejemplo de Custom Animatable Properties de Apple, tuve una buena idea de cómo funcionaba.

drawRect(_:)

Si no necesita acceder al valor actual de una propiedad de capa / vista durante su animación, puede simplemente usar drawRect(_:) para realizar su dibujo personalizado. Todo funcionará bien.

override func drawRect(rect: CGRect) { // your custom drawing code }

drawLayer(_:inContext:)

Digamos, por ejemplo, que quieres usar backgroundColor en tu código de dibujo personalizado:

override func drawRect(rect: CGRect) { let colorForCustomDrawing = self.layer.backgroundColor // your custom drawing code }

Cuando pruebe su código, notará que backgroundColor no devuelve el valor correcto (es decir, el valor actual) mientras una animación está en vuelo. En su lugar, devuelve el valor final (es decir, el valor de cuando se completa la animación).

Para obtener el valor actual durante la animación, debe acceder al backgroundColor del parámetro de layer pasado a drawLayer(_:inContext:) . Y también debe dibujar al parámetro de context .

Es muy importante saber que el self.layer una vista y el parámetro de layer pasado a drawLayer(_:inContext:) no siempre son la misma capa! Esta última podría ser una copia de la primera con animaciones parciales ya aplicadas a sus propiedades. De esta forma, puede acceder a los valores de propiedad correctos de las animaciones en vuelo.

Ahora el dibujo funciona como se esperaba:

override func drawLayer(layer: CALayer, inContext context: CGContext) { let colorForCustomDrawing = layer.backgroundColor // your custom drawing code }

Pero hay dos nuevos problemas: setNeedsDisplay() y varias propiedades como backgroundColor y opaque ya no funcionan para su vista. UIView ya no reenvía llamadas y cambios a su propia capa.

setNeedsDisplay() solo hace algo si su vista implementa drawRect(_:) . No importa si la función realmente hace algo, pero UIKit lo usa para determinar si usted hace un dibujo personalizado o no.

Es probable que las propiedades ya no funcionen porque ya no se UIView la propia implementación de drawLayer(_:inContext:) .

Entonces la solución es bastante simple. Simplemente llame a la implementación de superclase de drawLayer(_:inContext:) e implemente un drawRect(_:) vacío drawRect(_:) :

override func drawLayer(layer: CALayer, inContext context: CGContext) { super.drawLayer(layer, inContext: context) let colorForCustomDrawing = layer.backgroundColor // your custom drawing code } override func drawRect(rect: CGRect) { // Although we use drawLayer(_:inContext:) we still need to implement this method. // UIKit checks for its presence when it decides whether a call to setNeedsDisplay() is forwarded to its layer. }

Resumen

Use drawRect(_:) siempre que no tenga el problema de que las propiedades devuelven valores incorrectos durante una animación:

override func drawRect(rect: CGRect) { // your custom drawing code }

Utilice drawLayer(_:inContext:) y drawRect(_:) si necesita acceder al valor actual de las propiedades de vista / capa mientras se están animando:

override func drawLayer(layer: CALayer, inContext context: CGContext) { super.drawLayer(layer, inContext: context) let colorForCustomDrawing = layer.backgroundColor // your custom drawing code } override func drawRect(rect: CGRect) { // Although we use drawLayer(_:inContext:) we still need to implement this method. // UIKit checks for its presence when it decides whether a call to setNeedsDisplay() is forwarded to its layer. }