2012-04-24 10 views
7

En una subclase CALayer estoy trabajando en que tienen una propiedad personalizada que quiero animar de forma automática, es decir, suponiendo que la propiedad se llama "myProperty", quiero el siguiente código:Animación de propiedades personalizadas con actionForKey: ¿cómo obtengo el nuevo valor para la propiedad?

[myLayer setMyProperty:newValue]; 

Para causar una animación suave desde el valor actual hasta "newValue".

Utilizando el enfoque de reemplazar la acciónForKey: y needsDisplayForKey: (ver el siguiente código) Pude hacer que funcionara muy bien para simplemente interpolar entre el valor antiguo y el nuevo.

Mi problema es que quiero utilizar una duración de la animación o una ruta ligeramente diferente (o lo que sea) dependiendo del valor actual y el nuevo valor de la propiedad y no pude averiguar cómo obtener la nueva valor desde dentro actionForKey:

Gracias de antemano

@interface ERAnimatablePropertyLayer : CALayer { 
    float myProperty; 
} 
@property (nonatomic, assign) float myProperty; 
@end 
@implementation ERAnimatablePropertyLayer 
@dynamic myProperty; 

- (void)drawInContext:(CGContextRef)ctx { 
    ... some custom drawing code based on "myProperty" 
} 

- (id <CAAction>)actionForKey:(NSString *)key { 
    if ([key isEqualToString:@"myProperty"]) { 
     CABasicAnimation *theAnimation = [CABasicAnimation animationWithKeyPath:key]; 
     theAnimation.fromValue = [[self presentationLayer] valueForKey:key]; 

     ... I want to do something special here, depending on both from and to values... 


     return theAnimation; 
     } 
    return [super actionForKey:key]; 
    } 

+ (BOOL)needsDisplayForKey:(NSString *)key { 
    if ([key isEqualToString:@"myProperty"]) 
     return YES; 
    return [super needsDisplayForKey:key]; 
    } 
    @end 

Respuesta

1

puede almacenar los valores antiguos y nuevos en CATransaction.

-(void)setMyProperty:(float)value 
{ 
    NSNumber *fromValue = [NSNumber numberWithFloat:myProperty]; 
    [CATransaction setValue:fromValue forKey:@"myPropertyFromValue"]; 

    myProperty = value; 

    NSNumber *toValue = [NSNumber numberWithFloat:myProperty]; 
    [CATransaction setValue:toValue forKey:@"myPropertyToValue"]; 
} 

- (id <CAAction>)actionForKey:(NSString *)key { 
    if ([key isEqualToString:@"myProperty"]) { 
     CABasicAnimation *theAnimation = [CABasicAnimation animationWithKeyPath:key]; 
     theAnimation.fromValue = [[self presentationLayer] valueForKey:key]; 
     theAnimation.toValue = [CATransaction objectForKey:@"myPropertyToValue"]; 

     // here you do something special. 
    } 

    return [super actionForKey:key]; 
} 
+0

La implementación de un descriptor de acceso para la propiedad @dynamic (myProperty) hace que toda la magia desaparezca: CA simplemente deja de iniciar transacciones implícitas para la propiedad. – zrxq

+0

Vale la pena saberlo, pero dado que el OP quiere una animación explícita, diría que no es un problema. – Simon

+0

OP quiere uno implícito. "Tengo una propiedad personalizada que quiero animar automáticamente". Además, myProperty es @dynamic en el fragmento original. – zrxq

2

Debe evitar getters y setters personalizados para las propiedades que desea animar.

Anula el método didChangeValueForKey:. Úselo para establecer el valor del modelo para la propiedad que desea animar.

No configure la función toValue en la animación de acción.

@interface MyLayer: CALayer 

    @property (nonatomic) NSUInteger state; 

@end 

-

@implementation MyLayer 

@dynamic state; 

- (id<CAAction>)actionForKey: (NSString *)key { 

    if([key isEqualToString: @"state"]) 
    { 
     CABasicAnimation * bgAnimation = [CABasicAnimation animationWithKeyPath: @"backgroundColor"]; 
     bgAnimation.fromValue = [self.presentationLayer backgroundColor]; 
     bgAnimation.duration = 0.4; 

     return bgAnimation; 
    } 

    return [super actionForKey: key]; 
} 

- (void)didChangeValueForKey: (NSString *)key { 

    if([key isEqualToString: @"state"]) 
    { 
     const NSUInteger state = [self valueForKey: key]; 
     UIColor * newBackgroundColor; 

     switch (state) 
     { 
      case 0: 
       newBackgroundColor = [UIColor redColor]; 
       break; 

      case 1: 
       newBackgroundColor = [UIColor blueColor]; 
       break; 

      case 2: 
       newBackgroundColor = [UIColor greenColor]; 
       break; 

      default: 
       newBackgroundColor = [UIColor purpleColor]; 
     } 

     self.backgroundColor = newBackgroundColor.CGColor; 
    } 

    [super didChangeValueForKey: key]; 
} 

@end 
+0

En sus accesadores personalizados, tendrá que llamar a [self willChangeValueForKey: clave], establezca el valor y luego llame [self didChangeValueForKey: key]. – SG1

2

Core Animation llama actionForKey: antes de actualizar el valor de la propiedad. Ejecuta la acción después de actualizar el valor de propiedad enviándolo runActionForKey:object:arguments:. La implementación CAAnimation de runActionForKey:object:arguments: solo llama al [object addAnimation:self forKey:key].

En lugar de devolver la animación desde actionForKey:, puede devolver CAAction que, cuando se ejecuta, crea e instala una animación. Algo como esto:

@interface MyAction: NSObject <CAAction> 
@property (nonatomic, strong) id priorValue; 
@end 

@implementation MyAction 

- (void)runActionForKey:(NSString *)key object:(id)anObject arguments:(NSDictionary *)dict { 
    ERAnimatablePropertyLayer *layer = anObject; 
    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:key]; 
    id newValue = [layer valueForKey:key]; 
    // Set up animation using self.priorValue and newValue to determine 
    // fromValue and toValue. You could use a CAKeyframeAnimation instead of 
    // a CABasicAnimation. 
    [layer addAnimation:animation forKey:key]; 
} 

@end 

@implementation ERAnimatablePropertyLayer 

- (id <CAAction>)actionForKey:(NSString *)key { 
    if ([key isEqualToString:@"myProperty"]) { 
     MyAction *action = [[MyAction alloc] init]; 
     action.priorValue = [self valueForKey:key]; 
     return action; 
    } 
    return [super actionForKey:key]; 
} 

Usted puede encontrar un ejemplo práctico de una técnica similar (en Swift, animando cornerRadius) in this answer.

+0

¡Excelente respuesta! – Johannes

Cuestiones relacionadas