Animatable custom property not working in Swift

I have what I believe is equivalent code--for enabling implicit animation on a custom CALayer subclass property--behaving correctly in Objective-C and incorrectly in Swift.


@implementation CustomLayer
@dynamic thickness;
+(BOOL)needsDisplayForKey:(NSString*)key {
  return [key isEqual: @"thickness"];
}
-(id<CAAction>)actionForKey:(NSString*)key {
  NSLog(@"%@", key);
  if([key isEqual:@"thickness"]) {
  CABasicAnimation *anim = [[CABasicAnimation alloc] init];
  anim.fromValue = [[self presentationLayer] valueForKey:key];
  anim.duration = 2.0;
  return anim;
  } else {
  return [super actionForKey:key];
  }
}
-(void)drawInContext:(nonnull CGContextRef)ctx {
  CGContextSetLineWidth(ctx, [self thickness]);
  CGContextStrokeEllipseInRect(ctx, [self bounds]);
}
@end

// ...


[[self layer] setThickness:20.0]; // this triggers animation as expected


class CustomLayer : CALayer {
  dynamic var thickness : CGFloat = 1.0
  override func drawInContext(ctx: CGContext) {
  CGContextSetLineWidth(ctx, thickness)
  CGContextStrokeEllipseInRect(ctx, bounds)
  }
  override func actionForKey(event: String) -> CAAction? {
  print(event)
  if event == "thickness" {
  let anim = CABasicAnimation()
  anim.fromValue = presentationLayer()?.thickness
  anim.duration = 2.0
  return anim
  } else {
  return super.actionForKey(event)
  }
  }
  class override func needsDisplayForKey(key:String) -> Bool {
  if key == "thickness" {
  return true
  } else {
  return super.needsDisplayForKey(key)
  }
  }
}

// ...

layer.thickness = 20.0 // nothing happens


Am I doing something wrong?

Replies

@dynamic in Objective-C and dynamic in Swift are not equivalent. @dynamic in Objective-C is effectively a means to prevent the compiler from synthesizing a property, while dynamic in Swift says to always use dynamic dispatch for a particular property. Effectively Swift is still implementing the accessors, which prevents the custom property from working with Core Animation's support.


You will probably have to keep this implemented in Objective-C to work correctly, or find another way to do what you want.

Though I know it's not the intent of the annotation, chris2 could use @NSManaged to direct Swift not to synthesize accessors. Ultimately, I have to imagine that CALayer's implicit animation API will be reworked for Swift at some point. It relys very heavily on the Objective-C runtime, resolving property accessors at runtime, which will become increasingly undesirable as Swift moves farther from that runtime.

The "right" way to go about this would probably be to implement the property normally, then use didSet to call actionForKey: and apply that action.