2009-10-01 7 views
100

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.¿Cuál es la forma más sólida de forzar el redibujado de UIView?

Pero.

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

He intentado [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í es el fragmento de código que se alimenta de 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]; 

Cheers, Doug

Respuesta

176

El garantizada, forma sólida como una roca para forzar un UIView que volver a hacer es [myView setNeedsDisplay]. Si tienes problemas con eso, es muy probable toparse con uno de estos temas:

  • que está llamando antes de que realmente tiene los datos, o su -drawRect: es el exceso de almacenamiento en caché de algo.

  • Está esperando que la vista dibuje en el momento en que llama 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 "un poco de lógica, dibujar, algunas más lógica", entonces usted necesita para poner el "poco más lógica" en un método separado e invocar usando -performSelector:withObject:afterDelay: con un retraso de 0. pondrá "algo más de lógica" después del próximo ciclo de sorteo. Consulte this question para ver un ejemplo de ese tipo de código y un caso en el que podría ser necesario (aunque, por lo general, lo mejor es buscar otras soluciones, ya que complica el código).

Si no cree que las cosas se van a dibujar, ponga un punto de interrupción en -drawRect: y vea cuándo le llaman. Si llama al -setNeedsDisplay, pero no se llama al -drawRect: en el próximo 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.

+0

Rob, Aquí está mi lista de verificación. 1) ¿Tengo los datos? Sí. Creo la vista en un método - hideSpinner - llamado en el hilo principal de la conexiónDidFinishLoading: thusly: [self performSelectorOnMainThread: @selector (hideSpinner) withObject: nil waitUntilDone: NO]; 2) No necesito dibujar al instante. Solo lo necesito Hoy. Actualmente, es completamente aleatorio y está fuera de mi control. [myView setNeedsDisplay] no es confiable. Incluso he llegado a llamar a [myView setNeedsDisplay] en viewDidAppear :. Nuthin '. Cocoa simplemente me ignora. Maddening !! – dugla

+0

Al mirar tu código, lo primero que debes asegurarte es que contatinerView esté en la pantalla. Ponga algo más en él (un UILabel por ejemplo) y vea si eso funciona. En segundo lugar, asegúrese de que self.containerView no sea nulo. Una causa principal de "no pasa nada" es enviar un mensaje a cero. No debería necesitar un setNeedsDisplay aquí, pero si lo hiciera sería más típico enviarlo a containerView en lugar de a chromosomeBlockView. Le está pidiendo al contenedorView que se rediseñe a sí mismo y sus subvistas (que ha reorganizado), una de las cuales es chromosomeBlockView. –

+0

He descubierto que con este enfoque a menudo hay un retraso entre 'setNeedsDisplay' y la llamada real de' drawRect: '. Si bien la confiabilidad de las llamadas está aquí, no llamaría a esto la solución "más robusta": una solución de dibujo sólida debería, en teoría, hacer todo el diseño inmediatamente antes de volver al código del cliente que exige el sorteo. –

49

Tuve un problema con un gran retraso 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.

+0

Este fue definitivamente mi error. Tenía la impresión de que estaba ejecutando el hilo principal, pero no fue hasta que NSLogged NSThread.isMainThread que me di cuenta de que había un caso de esquina donde NO estaba haciendo los cambios de capa en el hilo principal. ¡Gracias por salvarme de sacarme el pelo! –

4

Tuve el mismo problema, y ​​todas las soluciones de SO o Google no funcionaron para mí. Por lo general, setNeedsDisplay funciona, pero cuando no ...
He intentado llamar al setNeedsDisplay de la vista de todas las formas posibles, 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á esta vez. Y la única solución que he encontrado es llamar de forma manual después de algún tiempo, para que cualquier cosa que se hace con el sorteo pasarán, como esto:

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 es necesario volver a dibujar la vista muy a menudo De lo contrario, si está haciendo algo de movimiento (acción), generalmente no hay problemas con solo llamar al setNeedsDisplay.

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

+1

Gracias, eso me ayudó después de 2 horas de investigación :) – Alex

12

La devolución de dinero garantizado, camino de hormigón armado sólida para forzar el fin de dibujar de forma sincrónica(antes de regresar al código de llamada) es configurar las interacciones del CALayer 's con su UIView subclase.

En la subclase UIView, crear un método - display que le dice a la capa que “sí lo necesita pantalla”, luego “para que así sea”:

/// 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 poner en práctica un método - drawLayer:inContext: que 'll llamar a su método de dibujo privada/interna (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 crear su método personalizado - internalDrawWithRect:, junto con la prueba de fallos - 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 acaba de llamar [myView display] cada vez que realmente-realmente lo necesita para dibujar. - display indicará CALayer a displayIfNeeded, que volverá a llamar sincrónicamente a nuestro - drawLayer:inContext: y realizará el sorteo en - internalDrawWithRect:, actualizando la imagen con lo dibujado en el contexto antes de continuar.


Este enfoque es similar al @ RobNapier está arriba, pero tiene la ventaja de llamar - displayIfNeeded además de - setNeedsDisplay, lo que hace que sea síncrona.

Esto es posible porque CALayer s exponer más funcionalidad de elaboración de UIView s capas do- son de nivel inferior a puntos de vista y explícitamente diseñadas con el propósito de dibujo altamente configurable dentro del diseño, y (como muchas cosas en Cocoa) son diseñado para ser utilizado de manera flexible (como una clase para padres, o como un delegador, o como un puente hacia otros sistemas de dibujo, o simplemente por su cuenta).

Más información sobre la capacidad de configuración de CALayer s se puede encontrar en Setting Up Layer Objects section of the Core Animation Programming Guide.

+0

Tenga en cuenta que la documentación de 'drawRect:' dice explícitamente "Nunca debe llamar directamente a este método usted mismo". Además, la 'pantalla' de CALayer dice explícitamente "No llamar a este método directamente". Si desea dibujar sincrónicamente los "contenidos" de las capas directamente, no es necesario violar estas reglas. Puede dibujar en los "contenidos" de la capa en cualquier momento que desee (incluso en los hilos de fondo). Solo agrega una subcapa a la vista para eso. Pero eso es diferente a ponerlo en la pantalla, que necesita esperar hasta el momento correcto de composición. –

+0

Digo que agregue una subcapa, ya que los documentos también advierten acerca de interferir directamente con el 'contenido' de la capa de UIView (" Si el objeto de capa está vinculado a un objeto de vista, debe evitar establecer el contenido de esta propiedad directamente. y las capas suelen dar como resultado que la vista reemplace el contenido de esta propiedad durante una actualización posterior. ") No estoy recomendando particularmente este enfoque; el dibujo prematuro socava el rendimiento y la calidad del dibujo. Pero si lo necesita por alguna razón, entonces 'contents' es cómo obtenerlo. –

+0

@RobNapier Point tomado con la llamada 'drawRect:' directamente. No fue necesario demostrar esta técnica, y se ha corregido. –

0

Bueno, sé que esto podría ser un gran cambio o incluso no es 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 en que lo hace es en el UITableViewdidSelectRowAtIndexPath que le pide 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 un poco de indicador de actividad, para el indicador de carga sencilla comprobación https://github.com/jdg/MBProgressHUD

Cuestiones relacionadas