2012-08-29 10 views
7

tengo que guardar capturas de pantalla de mi aplicación, por lo que he establecido un código como éste, que funciona:Cómo hacer Un CALayer en el fondo

- (void)renderScreen { 
    UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow]; 

    CGSize outputSize = keyWindow.bounds.size; 
    UIGraphicsBeginImageContext(outputSize); 
    CGContextRef context = UIGraphicsGetCurrentContext(); 

    CGContextSaveGState(context); 
    CALayer *layer = [keyWindow layer]; 
    [layer renderInContext:context]; 
    CGContextRestoreGState(context); 

    UIImage *screenImage = UIGraphicsGetImageFromCurrentImageContext(); 
    UIGraphicsEndImageContext(); 

    // now save the screen image, etc... 
} 

Sin embargo, cuando la imagen de la pantalla se convierte en complejos (lotes de vistas), el renderInContext puede tomar hasta 0.8 segundos en un iPad 3, y la interfaz de usuario se bloquea durante ese tiempo, lo que interfiere con alguna otra funcionalidad. Así que moví la prestación de un subproceso de fondo, como esto:

- (void)renderScreen { 
    UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow]; 
    CALayer *layer = [keyWindow layer]; 
    [self performSelectorInBackground:@selector(renderLayer:) withObject:layer]; 
} 

- (void)renderLayer:(CALayer *)layer { 
    UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow]; 

    CGSize outputSize = keyWindow.bounds.size; 
    UIGraphicsBeginImageContext(outputSize); 
    CGContextRef context = UIGraphicsGetCurrentContext(); 

    CGContextSaveGState(context); 
    [layer renderInContext:context]; 
    CGContextRestoreGState(context); 

    UIImage *screenImage = UIGraphicsGetImageFromCurrentImageContext(); 
    UIGraphicsEndImageContext(); 

    // now save the screen image, etc... 
} 

Eso permite que la interfaz funcione sin problemas de nuevo, pero en ocasiones provoca un accidente con EXC_BAD_ACCESS en la línea renderInContext. Traté de verificar la capa! = Nil y [layer respondsToSelector: @selector (renderInContext :)] primero, para poder evitar el bloqueo, pero ambas condiciones siempre son verdaderas.

Luego leí this SO comment, indicando que una capa podría mutar antes de que se ejecute la operación en segundo plano y sugiriendo enviar una copia de la capa a la operación de fondo. This SO answer y this one me inició, y que terminó con esta categoría para añadir un método de copia de CALayer:

#import "QuartzCore/CALayer.h" 

@interface CALayer (CALayerCopyable) 
- (id)copy; 
@end 

@implementation CALayer (CALayerCopyable) 

- (id)copy { 
    CALayer *newLayer = [CALayer layer]; 
    newLayer.actions = [self.actions copy]; 
    newLayer.anchorPoint = self.anchorPoint; 
    newLayer.anchorPointZ = self.anchorPointZ; 
    newLayer.backgroundColor = self.backgroundColor; 
    //newLayer.backgroundFilters = [self.backgroundFilters copy]; // iOS 5+ 
    newLayer.borderColor = self.borderColor; 
    newLayer.borderWidth = self.borderWidth; 
    newLayer.bounds = self.bounds; 
    //newLayer.compositingFilter = self.compositingFilter; // iOS 5+ 
    newLayer.contents = [self.contents copy]; 
    newLayer.contentsCenter = self.contentsCenter; 
    newLayer.contentsGravity = [self.contentsGravity copy]; 
    newLayer.contentsRect = self.contentsRect; 
    //newLayer.contentsScale = self.contentsScale; // iOS 4+ 
    newLayer.cornerRadius = self.cornerRadius; 
    newLayer.delegate = self.delegate; 
    newLayer.doubleSided = self.doubleSided; 
    newLayer.edgeAntialiasingMask = self.edgeAntialiasingMask; 
    //newLayer.filters = [self.filters copy]; // iOS 5+ 
    newLayer.frame = self.frame; 
    newLayer.geometryFlipped = self.geometryFlipped; 
    newLayer.hidden = self.hidden; 
    newLayer.magnificationFilter = [self.magnificationFilter copy]; 
    newLayer.mask = [self.mask copy]; // property is another CALayer 
    newLayer.masksToBounds = self.masksToBounds; 
    newLayer.minificationFilter = [self.minificationFilter copy]; 
    newLayer.minificationFilterBias = self.minificationFilterBias; 
    newLayer.name = [self.name copy]; 
    newLayer.needsDisplayOnBoundsChange = self.needsDisplayOnBoundsChange; 
    newLayer.opacity = self.opacity; 
    newLayer.opaque = self.opaque; 
    newLayer.position = self.position; 
    newLayer.rasterizationScale = self.rasterizationScale; 
    newLayer.shadowColor = self.shadowColor; 
    newLayer.shadowOffset = self.shadowOffset; 
    newLayer.shadowOpacity = self.shadowOpacity; 
    newLayer.shadowPath = self.shadowPath; 
    newLayer.shadowRadius = self.shadowRadius; 
    newLayer.shouldRasterize = self.shouldRasterize; 
    newLayer.style = [self.style copy]; 
    //newLayer.sublayers = [self.sublayers copy]; // this line makes the screen go blank 
    newLayer.sublayerTransform = self.sublayerTransform; 
    //newLayer.superlayer = self.superlayer; // read-only 
    newLayer.transform = self.transform; 
    //newLayer.visibleRect = self.visibleRect; // read-only 
    newLayer.zPosition = self.zPosition; 
    return newLayer; 
} 

@end 

Entonces puse al día renderScreen para enviar una copia de la capa de renderLayer:

- (void)renderScreen { 
    UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow]; 
    CALayer *layer = [keyWindow layer]; 
    CALayer *layerCopy = [layer copy]; 
    [self performSelectorInBackground:@selector(renderLayer:) withObject:layerCopy]; 
} 

Cuando ejecuto este código, todas las imágenes de pantalla son de color blanco claro. Obviamente mi método de copia no es correcto. Entonces, ¿alguien me puede ayudar con alguna de las siguientes soluciones posibles?

  1. Cómo escribir un método de copia para CALayer que realmente funciona?
  2. ¿Cómo comprobar que una capa pasada a un proceso en segundo plano es un objetivo válido para renderInContext?
  3. ¿Alguna otra forma de renderizar capas complejas sin bloquear la interfaz?

ACTUALIZACIÓN: Reescribí mi categoría de CALayerCopyable en función de la sugerencia de Rob Napier para utilizar initWithLayer. Simplemente copiando la capa todavía me dio un resultado en blanco, así que agregué un método para copiar recursivamente todas las subcapas. Todavía, sin embargo, obtener la llanura blanca de salida:

#import "QuartzCore/CALayer.h" 

@interface CALayer (CALayerCopyable) 
- (id)copy; 
- (NSArray *)copySublayers:(NSArray *)sublayers; 
@end 

@implementation CALayer (CALayerCopyable) 

- (id)copy { 
    CALayer *newLayer = [[CALayer alloc] initWithLayer:self]; 
    newLayer.sublayers = [self copySublayers:self.sublayers]; 
    return newLayer; 
} 

- (NSArray *)copySublayers:(NSArray *)sublayers { 
    NSMutableArray *newSublayers = [NSMutableArray arrayWithCapacity:[sublayers count]]; 
    for (CALayer *sublayer in sublayers) { 
     [newSublayers addObject:[sublayer copy]]; 
    } 
    return [NSArray arrayWithArray:newSublayers]; 
} 

@end 
+2

Su hilo de fondo * no puede * tocar 'UIWindow'. Necesitas pasar el tamaño directamente. –

+0

De acuerdo, gracias, para mis pruebas inmediatas, ahora estoy codificando el tamaño de la ventana. Dependiendo de la solución con la que termine, descubriré una buena forma de pasar esto de forma dinámica. Esto no afecta mi problema actual, sin embargo. – arlomedia

+0

¿Terminaste con una buena solución para esto? –

Respuesta

2

Para este propósito, que haría uso de initWithLayer: en lugar de crear su propio método de copia. initWithLayer: es explícitamente para crear "instantáneas de capas, por ejemplo, para el método presentationLayer".

Es posible que también necesite crear copias de las subcapas. No recuerdo de inmediato si initWithLayer: hace eso por usted. Pero initWithLayer: es cómo funciona Core Animation, por lo que está optimizado para problemas como este.

+0

Intenté initWithLayer por sí mismo, en lugar de mi método de copia, y aún así terminé con una imagen en blanco. También lo probé en lugar de [capa de CALayer] en mi método de copia, y luego copié todas las propiedades como antes, pero todavía obtuve una imagen blanca normal. La documentación dice "no use este método para inicializar una nueva capa con el contenido de una capa existente", por lo que no estaba seguro de si valía la pena seguir. ¿Crees que debería intentar iterar a través de las subcapas de la capa original y copiarlas en la nueva capa con initWithLayer? – arlomedia

+0

Intenté copiar todas las subcapas también, pero aún obtuve salida en blanco. He actualizado la pregunta con el código que probé. – arlomedia

Cuestiones relacionadas