programming learn framework development desarrollador cuenta app ios uiwebview calayer cadisplaylink
http://www.lombax.it/documents/ImageMirror.ziphttp://www.lombax.it/documents/DirtyLayer.zip

framework - learn ios



La mejor manera de reflejar un UIWebView (2)

Necesito reflejar los CALayers de un UIWebView a un CALayer más pequeño. El CALayer más pequeño es esencialmente un pip del UIWebView más grande. Estoy teniendo dificultades para hacer esto. Lo único que se acerca es CAReplicatorLayer, pero dado que el original y la copia tienen que tener CAReplicatorLayer como padre, no puedo dividir el original y copiar en diferentes pantallas.

Una ilustración de lo que estoy tratando de hacer:

El usuario debe poder interactuar con el CALayer más pequeño y ambos deben estar sincronizados.

He intentado hacer esto con renderInContext y CADisplayLink. Desafortunadamente, hay cierto retraso / tartamudeo porque está intentando volver a dibujar cada fotograma, 60 veces por segundo. Necesito una forma de hacer el reflejo sin volver a dibujar en cada cuadro, a menos que algo haya cambiado realmente. Así que necesito una forma de saber cuándo el CALayer (o los CALayers infantiles) se ensucian.

No puedo simplemente tener dos UIWebView porque dos páginas pueden ser diferentes (el tiempo está apagado, el fondo es diferente, etc.). No tengo control sobre la página web que se muestra. Tampoco puedo mostrar la pantalla completa del iPad, ya que hay otros elementos en la pantalla que no deberían aparecer en la pantalla externa.

Tanto el CALayer más grande como el CALayer "pip" más pequeño deben coincidir sin problemas con fotograma a fotograma en iOS 6. No necesito admitir versiones anteriores.

La solución debe ser pasable de la tienda de aplicaciones.


Como está escrito en los comentarios, si la necesidad principal es saber CUÁNDO actualizar la capa (y no cómo), muevo mi respuesta original después de la línea "ANTIGUA RESPUESTA" y agrego lo que se comenta en los comentarios:

Primero (100% Apple Review Safe ;-)

  • Puede tomar "capturas de pantalla" periódicas de su UIView original y comparar los NSData resultantes (antiguos y nuevos) -> si los datos son diferentes, el contenido de la capa cambió. No hay necesidad de comparar las capturas de pantalla de RESOLUCIÓN COMPLETA, pero puedes hacerlo con una más pequeña para tener un mejor rendimiento

Segundo: el rendimiento es amigable y la revisión "teórica" ​​es segura ... pero no está segura: - /

Intento explicar cómo llegué a este código:

El objetivo principal es comprender cuándo se ensucia TileLayer (una subclase privada de CALayer utilizada por UIWebView).

El problema es que no se puede acceder directamente. Pero, puede usar el método swizzle para cambiar el comportamiento del método layerSetNeedsDisplay: en cada CALayer y subclases.

Debe asegurarse de evitar un cambio radical en el comportamiento original y hacer solo lo necesario para agregar una "notificación" cuando se llame al método.

Cuando haya detectado con éxito cada layerSetNeedsDisplay: call, lo único que queda es comprender "cuál es" el CALayer involucrado -> si es el TileLayer interno de UIWebView, activamos una notificación "isDirty".

Pero no podemos recorrer el contenido de UIWebView y encontrar el TileLayer, por ejemplo, simplemente usando "isKindOfClass: [clase TileLayer]" seguramente le daremos un rechazo (Apple usa un analizador estático para verificar el uso de la API privada). ¿Qué puedes hacer?

Algo complicado como ... por ejemplo ... comparar el tamaño de la capa involucrada (la que llama layerSetNeedsDisplay :) con el tamaño de UIWebView? ;-)

Además, a veces, el UIWebView cambia el TileLayer secundario y usa uno nuevo, por lo que tiene que hacer esta verificación más veces.

Lo último: layerSetNeedsDisplay: no siempre se llama cuando simplemente se desplaza por UIWebView (si la capa ya está construida), por lo que tiene que usar UIWebViewDelegate para interceptar el desplazamiento / zoom.

Encontrará que el método swizzle es la razón del rechazo en algunas aplicaciones, pero siempre ha estado motivado con "ha cambiado el comportamiento de un objeto". En este caso, no cambia el comportamiento de algo, sino que simplemente intercepta cuando se llama a un método. Creo que puedes probarlo o contactar al Soporte de Apple para verificar si es legal, si no estás seguro.

ANTIGUA RESPUESTA

No estoy seguro de que el rendimiento sea lo suficientemente amigable, lo probé solo con ambas vistas en el mismo dispositivo y funciona bastante bien ... deberías probarlo con Airplay.

La solución es bastante simple: toma una "captura de pantalla" de UIWebView / MKMapView utilizando UIGraphicsGetImageFromCurrentImageContext. Haga esto 30/60 veces por segundo, y copie el resultado en un UIImageView (visible en la segunda pantalla, puede moverlo a donde desee).

Para detectar si la vista cambió y evitar el tráfico en el enlace inalámbrico, puede comparar los dos uiimages (el cuadro antiguo y el nuevo cuadro) byte a byte, y establecer el nuevo solo si es diferente del anterior. (si, funciona! ;-)

Lo único que no logré esta noche es hacer esta comparación rápida: si miras el código de ejemplo adjunto, verás que la comparación es realmente intensiva de CPU (porque usa UIImagePNGRepresentation () para convertir UIImage en NSData) y hace que toda la aplicación vaya tan lento. Si no usa la comparación (copiando cada fotograma), la aplicación es rápida y fluida (al menos en mi iPhone 5). Pero creo que hay muchas posibilidades de resolverlo ... por ejemplo, haciendo la comparación cada 4-5 cuadros, u optimizando la creación de NSData en segundo plano

Adjunto un proyecto de ejemplo: http://www.lombax.it/documents/ImageMirror.zip

En el proyecto, la comparación de cuadros está deshabilitada (si está comentada) adjunto el código aquí para futuras referencias:

// here you start a timer, 50fps // the timer is started on a background thread to avoid blocking it when you scroll the webview - (IBAction)enableMirror:(id)sender { dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul); //0ul --> unsigned long dispatch_async(queue, ^{ // 0.04f --> 25 fps NSTimer __unused *timer = [NSTimer scheduledTimerWithTimeInterval:0.02f target:self selector:@selector(copyImageIfNeeded) userInfo:nil repeats:YES]; // need to start a run loop otherwise the thread stops CFRunLoopRun(); }); } // this method create an UIImage with the content of the given view - (UIImage *) imageWithView:(UIView *)view { UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.opaque, 0.0); [view.layer renderInContext:UIGraphicsGetCurrentContext()]; UIImage *img = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return img; } // the method called by the timer -(void)copyImageIfNeeded { // this method is called from a background thread, so the code before the dispatch is executed in background UIImage *newImage = [self imageWithView:self.webView]; // the copy is made only if the two images are really different (compared byte to byte) // this comparison method is cpu intensive // UNCOMMENT THE IF AND THE {} to enable the frame comparison //if (!([self image:self.mirrorView.image isEqualTo:newImage])) //{ // this must be called on the main queue because it updates the user interface dispatch_queue_t queue = dispatch_get_main_queue(); dispatch_async(queue, ^{ self.mirrorView.image = newImage; }); //} } // method to compare the two images - not performance friendly // it can be optimized, because you can "store" the old image and avoid // converting it more and more...until it''s changed // you can even try to generate the nsdata in background when the frame // is created? - (BOOL)image:(UIImage *)image1 isEqualTo:(UIImage *)image2 { NSData *data1 = UIImagePNGRepresentation(image1); NSData *data2 = UIImagePNGRepresentation(image2); return [data1 isEqual:data2]; }


Creo que tu idea de usar CADisplayLink es buena. El principal problema es que estás intentando actualizar cada fotograma. Puede usar la propiedad frameInterval para disminuir la velocidad de cuadros automáticamente. Alternativamente, puede usar la propiedad de timestamp para saber cuándo ocurrió la última actualización.

Otra opción que podría funcionar: para saber si las capas están sucias, ¿por qué no tiene un objeto delegado de todas las capas, que obtendría su drawLayer:inContext: activado cada vez que cada capa necesita dibujo? A continuación, simplemente actualizar las otras capas en consecuencia.