0

I need to display information about the current fey frame image that is being displayed via a CAKeyframeAnimation. So when the image at "values" index 0 is displayed show information in some NSTextFields about that image and when it animates to index 1 show information about that image. Is it possible to do this?

CAKeyframeAnimation *theAnimation=[CAKeyframeAnimation animationWithKeyPath:@"contents"];
theAnimation.values = myView.productImages;
theAnimation.duration = 5.0;
theAnimation.repeatCount = HUGE_VALF;
theAnimation.calculationMode = kCAAnimationDiscrete;
theAnimation.fillMode = kCAFillModeRemoved;
[myView.exampleCALayer addAnimation:theAnimation forKey:@"contents"];
[myView.exampleCALayer setNeedsDisplay];
dbainbridge
  • 1,190
  • 11
  • 18

2 Answers2

1

Typically you can observe a property like position, for example, pretty easily using a timer or a display link while an animation is running, however, since it's the contents property you are trying to monitor, things are little more tricky. I would suggest that you animate your own custom property. You can then animate it in a group along with your existing contents animation and get updates whenever your custom property changes.

The steps go something like this:

  1. Declare a CALayer derived class
  2. Override these methods:

    - (id)initWithLayer:(id)layer

    + (BOOL)needsDisplayForKey:(NSString*)key

    - (void)drawInContext:(CGContextRef)ctx

  3. Create the property you want to override in the header

  4. Create a keyframe animation that animates your property

Here is what your layer might look like in its implementation:

@implementation MLImageLayer

- (id)initWithLayer:(id)layer
{
    if(self = [super initWithLayer:layer]) {
        if([layer isKindOfClass:[MLImageLayer class]]) {
            MLImageLayer *other = (MLImageLayer*)layer;
      [self setCounter:[other counter]];
        }
    }
    return self;
}

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

- (void)drawInContext:(CGContextRef)ctx
{
  DLog(@"Counter is: %d", _counter);
}

@end

Then, to actually animate the property, do this:

  CAKeyframeAnimation *counterAnimation = [CAKeyframeAnimation 
                                              animationWithKeyPath:@"counter"];
  [counterAnimation setDelegate:self];
  NSArray *values = @[@(0), @(1), @(2), @(3), @(4), @(5)];
  [counterAnimation setValues:values];
  [counterAnimation setDuration:5.0];
  [counterAnimation setRepeatCount:HUGE_VALF];
  [counterAnimation setCalculationMode:kCAAnimationDiscrete];

Now, back in your derived layer's -drawInContext: method, you can monitor the counter value and then respond accordingly.

This can be a bit tricky, though, since you're animating two properties at the same time. You'll have to use a group to get it to work right:

- (void)viewDidLoad
{
  [super viewDidLoad];

  _animationLayer = [MLImageLayer layer];
  [_animationLayer setBounds:CGRectMake(0.0f, 0.0f, 400.0f, 320.0f)];
  [_animationLayer setPosition:[[self view] center]];

  UIImage *image = [UIImage imageNamed:@"Countryside.jpg"];
  [_animationLayer setContents:(__bridge id)[image CGImage]];

  [[[self view] layer] addSublayer:_animationLayer];

  CAKeyframeAnimation *slideShowAnimation = [CAKeyframeAnimation animationWithKeyPath:@"contents"];
  [slideShowAnimation setValues:@[(id)[[UIImage imageNamed:@"Countryside.jpg"] CGImage],
   (id)[[UIImage imageNamed:@"Countryside-1.jpg"] CGImage],
   (id)[[UIImage imageNamed:@"Countryside-2.jpg"] CGImage],
   (id)[[UIImage imageNamed:@"Countryside-3.jpg"] CGImage],
   (id)[[UIImage imageNamed:@"Countryside-4.jpg"] CGImage],
   (id)[[UIImage imageNamed:@"Countryside-5.jpg"] CGImage]]];

  [slideShowAnimation setDuration:5.0];
  [slideShowAnimation setDelegate:self];
  [slideShowAnimation setRepeatCount:HUGE_VALF];
  [slideShowAnimation setCalculationMode:kCAAnimationDiscrete];

  CAKeyframeAnimation *counterAnimation = [CAKeyframeAnimation animationWithKeyPath:@"counter"];
  [counterAnimation setDelegate:self];
  NSArray *values = @[@(0), @(1), @(2), @(3), @(4), @(5)];
  [counterAnimation setValues:values];
  [counterAnimation setDuration:5.0];
  [counterAnimation setRepeatCount:HUGE_VALF];
  [counterAnimation setCalculationMode:kCAAnimationDiscrete];

  CAAnimationGroup *group = [CAAnimationGroup animation];
  [group setDuration:5.0];
  [group setRepeatCount:HUGE_VALF];
  [group setAnimations:@[slideShowAnimation, counterAnimation]];

  [_animationLayer addAnimation:group forKey:nil];
}

I posted a project up on github. It's written for iOS, but you should be able to adapt the Core Animation specific code in an OSX app.

Matt Long
  • 24,438
  • 4
  • 73
  • 99
  • Thanks for the awesome answer! I need to test it on OS X still but when I tried the github repo with the iOS 6.1 simulator it will log the same counter value about 20 times for each slide. Did you notice this? – dbainbridge Mar 15 '13 at 14:31
  • Yes. Your drawInContext is getting called at the layer's frame rate. You'll have to watch for *changes* to the counter value. – Matt Long Mar 15 '13 at 16:06
0

Wow, that's a great job!!! I'll use it…

Just one note, I'm on Xcode 5 and I noticed that only the first animation "slideShowAnimation" is fired.

I could fix it just by adding these two lines:

[slideShowAnimation setBeginTime:0];
[counterAnimation setBeginTime:0];

Thanks for sharing. - nIc

nIc
  • 31
  • 3