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?
- Cómo escribir un método de copia para CALayer que realmente funciona?
- ¿Cómo comprobar que una capa pasada a un proceso en segundo plano es un objetivo válido para renderInContext?
- ¿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
Su hilo de fondo * no puede * tocar 'UIWindow'. Necesitas pasar el tamaño directamente. –
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
¿Terminaste con una buena solución para esto? –