español - Método Quartz 2D DrawRect(iPhone)
cocoa framework (4)
Tengo 4 libros diferentes de iPhone / Cocoa / Core Animation / Objective-C delante de mí, junto con numerosos códigos de muestra de la web. Sin embargo, de alguna manera todavía siento que me falta algo de comprensión fundamental de cómo funciona el dibujo en Quartz 2D .
¿ drawRect()
pretende ser simplemente un gancho en el que ejecutar el código de dibujo? ¿O se supone que este método también redibuja regiones que están "dañadas" y necesitan volver a pintar? ¿Puedo dibujar mis cosas una vez y luego se "pegan", o debo volver a pintar toda la escena en cualquier momento a través de drawRect()
? El objeto Graphics2D de Java funciona de esta manera: debe dibujar toda su "imagen" cada vez que se invoca a paint (), por lo que debe estar preparado para reconstruirlo en cualquier momento (o almacenarlo en caché).
¿Cómo implementarías un simple programa de dibujo? ¿Tendría que "recordar" cada línea / punto / trazo que dibujó el usuario, y replicar cada vez que se llame a drawRect()
? ¿Qué hay de la representación "fuera de pantalla"; ¿Puedes hacer todo tu dibujo y luego llamar a [self setNeedsDisplay]
para que tus [self setNeedsDisplay]
en la pantalla?
Digamos que en respuesta al toque de un usuario, quiero poner una "X" en la pantalla donde retocó. La X debe permanecer allí, y cada nuevo toque produce otra X. ¿Debo recordar todas estas coordenadas de retoque y luego dibujarlas todas en drawRect()
?
EDITAR:
A menos que lo haya entendido mal, las respuestas de Joconor y Hector Ramos a continuación se contradicen entre sí. Y esa es una buena demostración de mi confusión con respecto a este tema. :-)
¿DrawRect () pretende ser simplemente un gancho en el que ejecutar el código de dibujo?
Está destinado a volver a dibujar la región (rect) que se le pasa, utilizando la pila de contexto de gráficos actual. No más.
¿O se supone que este método también redibuja regiones que están "dañadas" y necesitan volver a pintar?
No. Si las regiones sucias no se superponen, puede recibir múltiples invocaciones de drawRect:
con diferentes rectas pasadas a usted. Los extractos se invalidan usando setNeedsDisplayInRect:
Si solo se debe volver a dibujar una parte de la superficie de su vista, se le pedirá que dibuje esa parte cuando sea el momento de dibujar.
¿Puedo dibujar mis cosas una vez y luego se "pegan", o debo volver a pintar toda la escena en cualquier momento a través de drawRect ()?
No se ''pega''. Los extractos se invalidan durante la ejecución de su aplicación, y se le solicita que los vuelva a dibujar cuando el sistema de visualización necesite actualizar la pantalla. Repinta solo el rect que se solicita.
En algunos casos, una implementación simple puede (bastante perezosamente) invalidar el rect completo de la vista siempre que se invalide una parte. Eso suele ser malo, ya que normalmente requiere más dibujos de los necesarios, y es particularmente derrochador cuando las vistas no son opacas.
El objeto Graphics2D de Java funciona de esta manera: debe dibujar toda su "imagen" cada vez que se invoca a paint (), por lo que debe estar preparado para reconstruirlo en cualquier momento (o almacenarlo en caché).
No es así con AppKit o UIKit.
¿Cómo implementarías un simple programa de dibujo? ¿Tendría que "recordar" cada línea / punto / trazo que dibujó el usuario, y replicar cada vez que se llame a drawRect ()?
Tendrá que recordar el contexto (por ejemplo, cada línea / punto / trazo) requerido para dibujar su vista. Solo necesita dibujar la región que se solicita. Técnicamente, el sistema de gráficos no se quejaría si dibujara fuera de ese rect, pero eso podría conducir a artefactos.
Para la representación compleja, puede ser más fácil o más eficiente dibujar en un búfer externo (por ejemplo, mapa de bits), luego utilizar esa representación de mapa de bits drawrect:
para obtener cosas en pantalla mientras está en drawrect:
(ver también la respuesta de Brad para las capas)
¿Qué hay de la representación "fuera de pantalla"; ¿Puedes hacer todo tu dibujo y luego llamar a [self setNeedsDisplay] para que tus escritos se vacíen en la pantalla?
Si tu puedes hacerlo. Específicamente, renderizaría en un búfer externo (por ejemplo, un mapa de bits), cuando termine de renderizar en el mapa de bits, invalidará el rect que desea dibujar, luego dibujará en la pantalla usando los datos en el mapa de bits cuando se drawRect:
Digamos que en respuesta al toque de un usuario, quiero poner una "X" en la pantalla donde retocó. La X debe permanecer allí, y cada nuevo toque produce otra X. ¿Debo recordar todas estas coordenadas de retoque y luego dibujarlas todas en drawRect ()?
Bueno, tienes algunas opciones. Su propuesta es una (suponiendo que atraiga el rect que le pasaron). Otra sería crear una vista ''X'' y simplemente recordar los puntos necesarios para reconstruir la vista si necesita que esas X persistan en todos los lanzamientos. En muchos casos, puedes dividir fácilmente problemas complejos en capas (juego 2D simple):
- 1) La imagen de fondo con el horizonte.
- 2) Algunas cosas en primer plano que no cambian con frecuencia.
- 3) El personaje que usa el jugador para navegar a través del juego.
Entonces, la mayoría de los problemas se pueden dividir fácilmente para que no tenga que renderizar todo todo el tiempo. Esto reduce la complejidad y mejora el rendimiento si se hace bien. Si se hace mal, puede ser mucho peor.
Parte de la confusión entre varias referencias de Cocoa proviene de la introducción de vistas respaldadas por capas en Leopard. En el iPhone, todas las UIView están respaldadas por capas, donde las vistas de Leopard necesitan habilitar manualmente el respaldo de la capa.
Para una vista respaldada por capas, el contenido se dibuja una vez utilizando lo que haya proporcionado en drawRect()
, pero luego se almacena en la capa. La capa actúa como una textura rectangular, por lo que cuando mueve la vista respaldada por capas o la cubre, no es necesario volver a dibujar, la textura simplemente se mueve a esa ubicación a través de la GPU. A menos que establezca la needsDisplayOnBoundsChange property
en YES
para una capa, cambiar el tamaño de la capa (o su vista que lo contiene) simplemente escalará el contenido. Esto puede generar gráficos borrosos en su vista o capa, por lo que es posible que desee forzar un rediseño en este caso. setNeedsDisplay
activará un redibujado manual del contenido de la vista o de la capa, y un recaching posterior de ese contenido en la capa.
Para un rendimiento óptimo, se sugiere que evite las llamadas frecuentes a drawRect
, porque el dibujo de cuarzo y recaching en una capa son operaciones costosas. Lo mejor es tratar de hacer la animación usando capas separadas que puedes mover o escalar.
Las referencias basadas en Cocoa que ha visto que se relacionan con el escritorio pueden asumir vistas no respaldadas por capa, que llaman drawRect: cada vez que se necesita actualizar la vista, ya sea por movimiento, escalado o por tener parte de la vista oscurecido Como dije, todas las UIView están respaldadas por capas, por lo que este no es el caso en el iPhone.
Dicho esto, para su aplicación de dibujo, una forma de hacerlo sería mantener una matriz de objetos dibujados y llamar a drawRect: cada vez que el usuario agrega algo nuevo, iterando sobre cada uno de los objetos dibujados anteriormente en orden. Podría sugerir un enfoque alternativo donde creas un nuevo UIView o CALayer para cada operación de dibujo. El contenido de esa operación de dibujo (línea, arco, X, etc.) será dibujado por la vista o capa individual. De esta forma, no tendrá que volver a dibujar todo con un nuevo toque, y podrá hacer una buena edición de estilo vectorial moviendo cada uno de los elementos dibujados independientemente de los demás. Para dibujos complejos, podría haber un poco de compromiso de memoria en esto, pero apostaría a que tendría un rendimiento de dibujo mucho mejor (uso mínimo de CPU y parpadeo).
Siempre esté preparado para dibujar el área apropiada de su vista cuando se llama drawRect:
Aunque el sistema puede amortiguar su vista, eso evitará que drawRect:
sea invocado. Si por alguna razón, el sistema tiene que invalidar el búfer, su método drawRect:
puede invocarse nuevamente. Además, drawRect:
se invocará para diferentes áreas de su vista a medida que se vuelvan visibles como resultado del desplazamiento y otras operaciones que afecten la visibilidad de las áreas de su vista.
drawRect () dibujará en el buffer fuera de pantalla. No necesita volver a dibujar cada vez que las regiones estén "dañadas", ya que el iPhone OS se encarga de manejar las capas de las vistas. Simplemente escriba una vez en el búfer y deje que el SO maneje el resto. Esto no es como otros entornos de programación donde necesita seguir redibujando cada vez que algo pasa sobre su vista.