¿Cuál es la forma más robusta de obligar a UIView a volver a dibujar?

Tengo una UITableView con una lista de elementos. Al seleccionar un elemento se presiona un control de vista que luego hace lo siguiente. del método viewDidLoad Apago una URLRequest para los datos requeridos por una de mis subvistas – una subclase de UIView con drawRect anulado. Cuando los datos llegan de la nube, empiezo a construir mi jerarquía de vistas. la subclase en cuestión pasa los datos y su método drawRect ahora tiene todo lo que necesita para renderizar.

Pero.

Como no llamo a drawRect explícitamente, Cocoa-Touch lo maneja, no tengo manera de informarle a Cocoa-Touch que realmente, realmente quiero que se renderice esta subclase de UIView. ¿Cuando? ¡Ahora estaría bien!

Lo intenté [myView setNeedsDisplay]. Esto a veces funciona. Muy irregular.

He estado luchando con esto durante horas y horas. ¿Podría alguien que por favor me brinde un enfoque sólido y garantizado para forzar una nueva versión de UIView?

Aquí está el fragmento de código que alimenta datos a la vista:

// Create the subview self.chromosomeBlockView = [[[ChromosomeBlockView alloc] initWithFrame:frame] autorelease]; // Set some properties self.chromosomeBlockView.sequenceString = self.sequenceString; self.chromosomeBlockView.nucleotideBases = self.nucleotideLettersDictionary; // Insert the view in the view hierarchy [self.containerView addSubview:self.chromosomeBlockView]; [self.containerView bringSubviewToFront:self.chromosomeBlockView]; // A vain attempt to convince Cocoa-Touch that this view is worthy of being displayed ;-) [self.chromosomeBlockView setNeedsDisplay]; 

Saludos, Doug

La forma segura y sólida de forzar una UIView para volver a renderizar es [myView setNeedsDisplay] . Si tiene problemas con eso, es probable que se encuentre con uno de estos problemas:

  • Lo está llamando antes de que realmente tenga los datos, o su -drawRect: está sobre-almacenamiento en caché de algo.

  • Esperas que la vista dibuje en el momento en que llamas a este método. Intencionalmente, no hay forma de exigir “dibujar ahora mismo” usando el sistema de dibujo Cocoa. Eso interrumpiría todo el sistema de composición de vistas, el rendimiento de la basura y probablemente crearía todo tipo de artefactos. Solo hay formas de decir “esto debe ser dibujado en el próximo ciclo de sorteo”.

Si lo que necesita es “algo de lógica, extracción, algo más de lógica”, entonces debe poner “algo más de lógica” en un método separado e invocarlo utilizando -performSelector:withObject:afterDelay: con un retraso de 0. Eso ponga “algo más de lógica” después del próximo ciclo de sorteo. Consulte esta pregunta para ver un ejemplo de ese tipo de código y un caso en el que podría ser necesario (aunque, en general, lo mejor es buscar otras soluciones, ya que complica el código).

Si no crees que las cosas se dibujan, pon un punto de interrupción en -drawRect: y mira cuando te llaman. Si está llamando a -setNeedsDisplay , pero -drawRect: no recibe llamadas en el siguiente ciclo de eventos, luego profundice en la jerarquía de vistas y asegúrese de que no está tratando de burlarlo. La exceso de inteligencia es la causa número 1 del mal dibujo en mi experiencia. Cuando crees que sabes cómo engañar al sistema para que haga lo que quieres, generalmente obtienes lo que no quieres.

Tuve un problema con una gran demora entre llamar a setNeedsDisplay y drawRect: (5 segundos). Resultó que llamé a setNeedsDisplay en un hilo diferente al hilo principal. Después de mover esta llamada al hilo principal, la demora desapareció.

Espero que esto sea de alguna ayuda.

El método de garantía de devolución de dinero garantizado, sólido sólido para forzar una vista para dibujar sincrónicamente (antes de volver al código de llamada) es configurar las CALayer del CALayer con su subclase UIView .

En su subclase UIView, cree un método de - display que le indique a la capa que ” sí, necesita mostrar “, luego ” para hacerlo así “:

 /// Redraws the view's contents immediately. /// Serves the same purpose as the display method in GLKView. /// Not to be confused with CALayer's `- display`; naming's really up to you. - (void)display { CALayer *layer = self.layer; [layer setNeedsDisplay]; [layer displayIfNeeded]; } 

También implemente un - drawLayer:inContext: que llamará a su método de dibujo privado / interno (que funciona ya que cada UIView es un CALayerDelegate) :

 /// Called by our CALayer when it wants us to draw /// (in compliance with the CALayerDelegate protocol). - (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)context { UIGraphicsPushContext(context); [self internalDrawWithRect:self.bounds]; UIGraphicsPopContext(); } 

Y cree su método personalizado - internalDrawWithRect: junto con a prueba de fallas - drawRect: ::

 /// Internal drawing method; naming's up to you. - (void)internalDrawWithRect:(CGRect)rect { // @FILLIN: Custom drawing code goes here. // (Use `UIGraphicsGetCurrentContext()` where necessary.) } /// For compatibility, if something besides our display method asks for draw. - (void)drawRect:(CGRect)rect { [self internalDrawWithRect:rect]; } 

Y ahora solo llame a [myView display] cada vez que realmente necesite realmente dibujar. - display le indicará al CALayer que displayIfNeeded , que devolverá la llamada sincrónicamente a nuestro - drawLayer:inContext: y realizará el dibujo en - internalDrawWithRect: actualizando el visual con lo dibujado en el contexto antes de continuar.


Este enfoque es similar al anterior de RobNapier, pero tiene la ventaja de llamar - displayIfNeeded además de - setNeedsDisplay , que lo hace síncrono.

Esto es posible porque CALayer expone más funcionalidades de dibujo que las UIView de UIView son de nivel inferior a las vistas y están diseñadas explícitamente para el diseño altamente configurable dentro del diseño, y (como muchas otras cosas en Cocoa) están diseñadas para ser utilizadas de forma flexible (como una clase para padres, o como un delegador, o como un puente hacia otros sistemas de dibujo, o solo por sí mismos).

Se puede encontrar más información sobre la CALayer de configuración de CALayer en la sección Configuración de los objetos de la capa de la Guía de progtwigción de Core Animation .

Tuve el mismo problema, y ​​todas las soluciones de SO o Google no funcionaron para mí. Por lo general, setNeedsDisplay funciona, pero cuando no …
Intenté llamar a setNeedsDisplay de la vista de todas las maneras posibles desde todos los hilos posibles, incluso sin éxito. Sabemos, como dijo Rob, que

“Esto debe ser dibujado en el próximo ciclo de sorteo”.

Pero por alguna razón no se dibujaría esta vez. Y la única solución que he encontrado es llamarla manualmente después de un tiempo, para que desaparezca cualquier cosa que bloquee el sorteo, como esta:

 dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.005 * NSEC_PER_SEC)); dispatch_after(popTime, dispatch_get_main_queue(), ^(void) { [viewToRefresh setNeedsDisplay]; }); 

Es una buena solución si no necesita la vista para volver a dibujar con frecuencia. De lo contrario, si estás haciendo algo de movimiento (acción), generalmente no hay problemas con solo llamar a setNeedsDisplay .

Espero que ayude a alguien que está perdido allí, como yo.

Bueno, sé que esto podría ser un gran cambio o incluso no adecuado para su proyecto, pero ¿consideró no realizar el impulso hasta que ya tenga los datos ? De esta forma, solo tendrá que dibujar la vista una vez y la experiencia del usuario también será mejor: la inserción se moverá cuando ya esté cargado.

La forma de hacerlo es en el UITableView didSelectRowAtIndexPath que solicita asíncronamente los datos. Una vez que reciba la respuesta, realice manualmente la segue y pase los datos a su viewController en prepareForSegue . Mientras tanto, es posible que desee mostrar algún indicador de actividad, para comprobar el indicador de carga simple https://github.com/jdg/MBProgressHUD