La representación CADISplayLink OpenGL rompe el comportamiento UIScrollView

Hay algunas preguntas similares en SO (enlaces al final), pero ninguna de ellas me ha permitido solucionar mi problema, así que aquí va:

Estoy usando el renderizado OpenGL para hacer una biblioteca de mosaico de imágenes y almacenamiento en caché para usar en un proyecto de juego, y quiero secuestrar la física de UIScrollView para permitir al usuario navegar alrededor de las imágenes (ya que tiene un buen comportamiento de rebote, podría bien usarlo). Así que tengo un UIScrollView que estoy usando para obtener la vista de representación de mis texturas, pero hay un problema: moverse en la vista de desplazamiento evita que CADisplayLink se active hasta que el usuario haya terminado de desplazarse (lo que se ve horrible). Una solución temporal ha sido usar NSRunLoopCommonModes en lugar del modo de ejecución predeterminado, pero desafortunadamente esto rompe algunos aspectos del comportamiento de la vista de desplazamiento en ciertos teléfonos en los que estoy probando (el 3GS y el simulador parecen funcionar bien, mientras que el iPhone4 y el 3G ‘t).

¿Alguien sabe cómo podría solucionar este choque entre CADisplayLink y UIScrollView, o saber cómo arreglar UIScrollView en otros modos de ejecución? Gracias por adelantado 🙂

Enlaces prometidos a preguntas similares: UIScrollView roto y detiene el desplazamiento con la representación de OpenGL (CADisplayLink relacionado, NSRunLoop)

La animación en la vista de OpenGL ES se congela cuando se arrastra UIScrollView en el iPhone

Es posible que las actualizaciones lentas en el hilo principal desencadenadas por CADisplayLink sean lo que está rompiendo el comportamiento de desplazamiento de UIScrollView aquí. Su renderizado de OpenGL ES puede tardar lo suficiente para que cada cuadro NSRunLoopCommonModes el tiempo de un UIScrollView cuando use NSRunLoopCommonModes para CADisplayLink.

Una forma de evitar esto es realizar sus acciones de renderización de OpenGL ES en una cadena de fondo utilizando una cola serie de Grand Central Dispatch. Hice esto en mi reciente actualización de Moléculas (cuyo código fuente se puede encontrar en ese enlace), y al probar con NSRunLoopCommonModes en mi CADisplayLink, no veo ninguna interrupción del comportamiento de desplazamiento nativo de una vista de tabla que está en pantalla al mismo tiempo que el renderizado.

Para esto, puede crear una cola de despacho serial de GCD y usarla para todas sus actualizaciones de representación en un contexto de OpenGL ES particular para evitar que dos acciones escriban en el contexto al mismo tiempo. Luego, dentro de su callback CADisplayLink puede usar un código como el siguiente:

 if (dispatch_semaphore_wait(frameRenderingSemaphore, DISPATCH_TIME_NOW) != 0) { return; } dispatch_async(openGLESContextQueue, ^{ [EAGLContext setCurrentContext:context]; // Render here dispatch_semaphore_signal(frameRenderingSemaphore); }); 

donde frameRenderingSemaphore se crea anteriormente de la siguiente manera:

 frameRenderingSemaphore = dispatch_semaphore_create(1); 

Este código solo agregará una nueva acción de representación de fotogtwigs en la cola si no se encuentra en medio de la ejecución. De esta forma, CADisplayLink puede activarse continuamente, pero no sobrecargará la cola con acciones de renderización pendientes si un cuadro tarda más de 1/60 de segundo en procesarse.

Una vez más, probé esto en mi iPad aquí y no encontré ninguna interrupción en la acción de desplazamiento de una vista de tabla, solo un poco de desaceleración, ya que la renderización de OpenGL ES consumió los ciclos de la GPU.

Mi solución simple es reducir a la mitad la velocidad de reproducción cuando el ciclo de ejecución está en modo de seguimiento. Todos mis UIScrollViews ahora funcionan sin problemas.

Aquí está el fragmento de código:

 - (void) drawView: (CADisplayLink*) displayLink { if (displayLink != nil) { self.tickCounter++; if(( [[ NSRunLoop currentRunLoop ] currentMode ] == UITrackingRunLoopMode ) && ( self.tickCounter & 1 )) { return; } /*** Rendering code goes here ***/ } } 

La respuesta en la siguiente publicación funciona muy bien para mí (parece ser bastante similar a la respuesta de Till):

UIScrollView pausa NSTimer hasta que termina el desplazamiento

Para resumir: deshabilite el bucle de renderizado CADisplayLink o GLKViewController cuando aparezca UIScrollView e inicie un NSTimer para ejecutar el bucle de actualización / renderización con la velocidad de fotogtwigs deseada. Cuando UIScrollView se descarta / elimina de la jerarquía de vistas, vuelva a habilitar el ciclo displayLink / GLKViewController.

En la subclase GLKViewController utilizo el siguiente código

en aparecer de UIScrollView:

 // disable GLKViewController update/render loop, it will be interrupted // by the UIScrollView of the MPMediaPicker self.paused = YES; updateAndRenderTimer = [NSTimer timerWithTimeInterval:1.0f/60.0f target:self selector:@selector(updateAndRender) userInfo:nil repeats:YES]; [[NSRunLoop mainRunLoop] addTimer:updateAndRenderTimer forMode:NSRunLoopCommonModes]; 

al descartar UIScrollView:

 // enable the GLKViewController update/render loop and cancel our own. // UIScrollView wont interrupt us anymore self.paused = NO; [updateAndRenderTimer invalidate]; updateAndRenderTimer = nil; 

Simple y efectivo. No estoy seguro si esto podría causar artefactos / rasgaduras de algún tipo ya que el renderizado está desacoplado de las actualizaciones de la pantalla, pero el uso de CADisplayLink con NSRunLoopCommonModes rompe totalmente el UIScrollView en nuestro caso. El uso de NSTimer se ve muy bien para nuestra aplicación y, definitivamente, mucho mejor que ninguna representación.

Aunque esta no es la solución perfecta, aún podría funcionar como una solución; Puede ignorar la disponibilidad del enlace de pantalla y usar NSTimer para actualizar su capa GL en su lugar.