iphone objective-c core-animation calayer bounds

iphone - Animar los límites de un CALayer w/redraws



objective-c core-animation (5)

Me pregunto cómo se podrían animar los límites de un CALayer, por lo que, en cada cambio de límites, la capa llama a drawInContext: He probado los 2 métodos siguientes en mi subclase CALayer:

  • Configurando las needsDisplayOnBoundsChange a YES
  • Devolviendo YES a la (BOOL)needsDisplayForKey:(NSString*)key + (BOOL)needsDisplayForKey:(NSString*)key para la clave de bounds

Tampoco trabajo. CALayer parece decidido a usar los contenidos originales de la capa y simplemente escalarlos según los contentsGravity de la contentsGravity (que, supongo, es para el rendimiento). ¿Es una solución para esto o me falta algo obvio?

EDIT: Y, por cierto, me di cuenta de que mi subclase CALayer personalizada no está llamando a initWithLayer: para crear un presentationLayer - extraño.

Gracias de antemano, Sam


Esta es tu clase personalizada:

@implementation MyLayer -(id)init { self = [super init]; if (self != nil) self.actions = [NSDictionary dictionaryWithObjectsAndKeys: [NSNull null], @"bounds", nil]; return self; } -(void)drawInContext:(CGContextRef)context { CGContextSetRGBFillColor(context, drand48(), drand48(), drand48(), 1); CGContextFillRect(context, CGContextGetClipBoundingBox(context)); } +(BOOL)needsDisplayForKey:(NSString*)key { if ([key isEqualToString:@"bounds"]) return YES; return [super needsDisplayForKey:key]; } @end

Estas son adiciones a la plantilla por defecto de xcode 4.2:

-(BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions { self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; // Override point for customization after application launch. self.window.backgroundColor = [UIColor whiteColor]; [self.window makeKeyAndVisible]; // create and add layer MyLayer *layer = [MyLayer layer]; [self.window.layer addSublayer:layer]; [self performSelector:@selector(changeBounds:) withObject:layer]; return YES; } -(void)changeBounds:(MyLayer*)layer { // change bounds layer.bounds = CGRectMake(0, 0, drand48() * CGRectGetWidth(self.window.bounds), drand48() * CGRectGetHeight(self.window.bounds)); // call "when idle" [self performSelector:@selector(changeBounds:) withObject:layer afterDelay:0]; }

----------------- editado:

Ok ... esto no es lo que pediste :) Lo siento: |

----------------- editado (2):

¿Y por qué necesitarías algo así? Se puede usar la (void)display , pero la documentación dice que está ahí para configurarse. self.contents ...


Me estoy topando con el mismo problema recientemente. Esto es lo que descubrí:

Hay un truco que puedes hacer, que es animar una sombra de límites como:

var shadowBounds: CGRect { get { return bounds } set { bounds = newValue} }

luego anula CALayer''s + needsDisplayForKey :.

Sin embargo, esto PUEDE NO ser lo que quiere hacer si su dibujo depende de los límites. Como ya notó, la animación central simplemente ajusta el contenido de la capa para animar los límites. Esto es cierto incluso si realiza el truco anterior, es decir, los contenidos se escalan incluso si los límites cambiaron durante la animación. El resultado es que tu animación de dibujos parece inconsistente.

¿Como resolverlo? Dado que el contenido se escala, puede calcular los valores de las variables personalizadas que determinan su dibujo al escalarlas de manera inversa para que su dibujo en el contenido final pero escalado se vea igual al original y sin escalar, luego establezca los valores de FromValues ​​a estos valores, Los valores a sus valores antiguos, animarlos al mismo tiempo con límites. Si se van a cambiar los valores finales, establezca los valores de toValues ​​en estos valores finales. Debe animar al menos una variable personalizada para provocar el redibujado.


No estoy seguro de que la configuración de viewFlags sea efectiva. La segunda solución definitivamente no funcionará:

La implementación por defecto devuelve NO. Las subclases deben * llamarse super para las propiedades definidas por la superclase. (Por ejemplo, * no intente devolver SÍ a las propiedades implementadas por CALayer, * hacer tendrá resultados indefinidos).

Debe configurar el modo de contenido de la vista en UIViewContentModeRedraw:

UIViewContentModeRedraw, //redraw on bounds change (calls -setNeedsDisplay)

Consulte la documentación de Apple sobre la provisión de contenido con CALayer''s . Recomiendan usar la propiedad delegada de CALayer en lugar de la subclase, que puede ser mucho más fácil de lo que estás intentando ahora.


No sé si esto califica por completo como una solución a su pregunta, pero pude hacer que esto funcionara.

Primero dibujé mi imagen de contenido en un CGImageRef .

Luego -display método -display de mi capa -drawInContext: OF -drawInContext: En él puse el contents en el CGImage pre-renderizado, y funcionó.

Finalmente, su capa también necesita cambiar el contentsGravity predeterminado contentsGravity a algo como @"left" para evitar que la imagen del contenido se dibuje a escala.

El problema que estaba teniendo era que el contexto que se pasaba a -drawInContext: era del tamaño inicial de la capa, no del tamaño final posterior a la animación. (Puede verificar esto con los métodos CGBitmapContextGetWidth y CGBitmapContextGetHeight ).

A mis métodos solo se les pide una vez para toda la animación, pero configurar los contenidos de la capa directamente con el método de -display permite pasar una imagen más grande que los límites visibles. El método drawInContext: no lo permite, ya que no puede dibujar fuera de los límites del contexto CGContext.

Para obtener más información sobre la diferencia entre los diferentes métodos de dibujo de capas, consulte http://www.apeth.com/iOSBook/ch16.html


Puede usar la técnica descrita aquí : anular el método CALayer''s +needsDisplayForKey: y volverá a dibujar su contenido en cada paso de la animación.