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


Answers

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

Question

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: :?




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).




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.
}



Related