2011-09-27 17 views
16

Me pregunto cómo se pueden 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:Animar los límites de un CALayer con redibujados

  • Configuración needsDisplayOnBoundsChange a YES
  • Volviendo YES para el + (BOOL)needsDisplayForKey:(NSString*)key para la bounds clave

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

EDIT: Y, dicho sea de paso, me di cuenta de que mi subclase personalizada CALayer no está llamando initWithLayer: para crear un presentationLayer - raro.

Gracias de antemano, Sam

+0

Otra cosa que no funciona: subclassing y anulación 'setFrame',' setBounds' y 'setPosition'. No son llamados durante la animación. –

+0

No te entiendo del todo. ¿Qué estás tratando de animar? ¿Solo los límites de CALayer o algo más? Bounds animating es una tarea bastante simple, animando cuadros, más compleja. – beryllium

+0

Imagine que su capa contiene algo así como un botón con un gráfico de borde complejo pero independiente del tamaño. Si lo animas para que diga el doble de ancho, se animará usando la escala de mapa de bits, estirándose y pixelando a lo largo de la animación, incluso si tienes 'needsDisplayOnBoundsChange' YES. Solo el cuadro final se representará correctamente con 'drawInContext:'. –

Respuesta

1

no estoy seguro de que la fijación de los ViewFlags sería eficaz. La segunda solución definitivamente no funcionará:

La implementación predeterminada devuelve NO. Las subclases deben * llamar a super para las propiedades definidas por la superclase. (Por ejemplo, * no trate para volver SI para las características implementadas por CALayer, * Hacer la voluntad han indefinido resultados.)

Es necesario configurar el modo de contenido de la vista a UIViewContentModeRedraw:

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

Consulte Apple's documentation on providing content with CALayer's. Recomiendan usar la propiedad de delegado de CALayer en lugar de la subclase, lo que podría ser mucho más fácil que lo que estás intentando ahora.

+0

UIViewContentModeRedraw no hace que el contenido de la capa se vuelva a dibujar en cada paso de una animación. – titaniumdecoy

+0

¿De verdad? Hmmm ... ¿Sabes si los métodos CALayerDelegate se invocan durante la animación? –

+0

No, a menos que use la técnica que vinculé en mi respuesta (que es anular el método '+ needsDisplayForKey:' de CALayer) – titaniumdecoy

6

Puede utilizar el technique outlined here: reemplazar el método de CALayer +needsDisplayForKey: y va a volver a dibujar su contenido a cada paso de la animación.

+0

Esta es una gran respuesta. Proporcione más información sobre el enlace para que sepamos antes de hacer clic. –

+0

De hecho, este método hace que el método de redibujado se llame para cada cuadro hasta donde yo sé, aunque curiosamente el CALayer no muestra los resultados de esos redibujados, pero continúa con el contenido extendido que generó con anticipación. Aún así, es un gran paso adelante. –

+0

@AlexanderLjungberg: Este método debe volver a dibujar el contenido en cada paso de la animación. Creé un proyecto de prueba para confirmar que funciona. No estoy seguro de qué podría estar causando que tu capa no se vuelva a mostrar. – titaniumdecoy

-2

Esta es la 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 

Estos son adiciones a Xcode 4.2 plantilla por defecto:

-(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ía algo por el estilo? (void)display se puede utilizar, pero la documentación dice que está ahí para establecer self.contents ...

+0

Esto no funciona: + needsDisplayForKey no fuerza un redibujado para "límites" –

0

No sé si esto califica por completo como una solución a su pregunta, pero fue capaz de hacer que esto funcione.

Primero pre-dibuje mi imagen de contenido en un CGImageRef.

entonces pesaban más que las método de mi capa EN LUGAR DE-drawInContext:-display. En él establecí contents en la CGImage pretratada, y funcionó.

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

El problema que tenía era que el contexto que pasaba a -drawInContext: tenía el tamaño de inicio de la capa, no el tamaño final de post-animación. (Esto se puede comprobar con los métodos CGBitmapContextGetWidth y CGBitmapContextGetHeight.)

Mis métodos son todavía sólo llama una vez para toda la animación, pero el contenido del ajuste de la capa directamente con el método -display permite pasar una imagen mayor de los límites visibles . El método drawInContext: no permite esto, ya que no puede dibujar fuera de los límites del contexto CGContext.

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

0

estoy corriendo en el mismo problema recientemente. Esto es lo que descubrí:

Hay un truco que puede hacerlo, es decir la animación de una instantánea de los límites como:

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

entonces anular de CALayer + needsDisplayForKey :.

Sin embargo, esto NO PUEDE ser lo que desea hacer si su dibujo depende de los límites. Como ya habrás notado, la animación central simplemente escala el contenido de la capa para animar los límites. Esto es cierto incluso si hace el truco anterior, es decir, los contenidos se cambian aunque los límites cambien durante la animación. El resultado es que su animación de dibujos parece inconsistente.

¿Cómo resolverlo? Dado que el contenido está escalado, puede calcular los valores de las variables personalizadas que determinan su dibujo volviéndolo a escalar de forma que su dibujo sobre el contenido final pero a escala tenga el mismo aspecto que el original y sin escalar, luego configure los Valores de a estos valores, los valores a sus valores anteriores, animarlos al mismo tiempo con límites. Si se van a cambiar los valores finales, configure los valores a a estos valores finales. Debe animar al menos una variable personalizada para causar el redibujado.