views:

203

answers:

2

I want to switch the image shown in an NSImageView, but I want to animate that change. I've tried various methods to do this. Hopefully one of you could suggest one that might actually work. I'm working with Cocoa for Mac.

A: 

As far as I know, NSImageView doesn't support animating image changes. However, you can place a second NSImageView on top of the first one and animate hiding the old one and showing the new one. For example:

NSImageView *newImageView = [[NSImageView alloc] initWithFrame: [imageView frame]];
[newImageView setImageFrameStyle: [imageView imageFrameStyle]];
// anything else you need to copy properties from the old image view
// ...or unarchive it from a nib

[newImageView setImage: [NSImage imageNamed: @"NSAdvanced"]];
[[imageView superview] addSubview: newImageView
                       positioned: NSWindowAbove relativeTo: imageView];
[newImageView release];

NSDictionary *fadeIn = [NSDictionary dictionaryWithObjectsAndKeys:
             newImageView, NSViewAnimationTargetKey,
             NSViewAnimationFadeInEffect, NSViewAnimationEffectKey,
             nil];
NSDictionary *fadeOut = [NSDictionary dictionaryWithObjectsAndKeys:
             imageView, NSViewAnimationTargetKey,
             NSViewAnimationFadeOutEffect, NSViewAnimationEffectKey,
             nil];
NSViewAnimation *animation = [[NSViewAnimation alloc] initWithViewAnimations:
              [NSArray arrayWithObjects: fadeOut, fadeIn, nil]];
[animation setAnimationBlockingMode: NSAnimationBlocking];
[animation setDuration: 2.0];
[animation setAnimationCurve: NSAnimationEaseInOut];
[animation startAnimation];
[imageView removeFromSuperview];
imageView = newImageView;
[animation release];

If your view is big and you can require 10.5+, then you could do the same thing with Core Animation, which will be hardware accelerated and use a lot less CPU.

After creating newImageView, do something like:

[newImageView setAlphaValue: 0];
[newImageView setWantsLayer: YES];
// ...
[self performSelector: @selector(animateNewImageView:) withObject: newImageView afterDelay: 0];

- (void)animateNewImageView:(NSImageView *)newImageView;
{
    [NSAnimationContext beginGrouping];
    [[NSAnimationContext currentContext] setDuration: 2];
    [[newImageView animator] setAlphaValue: 1];
    [[imageView animator] setAlphaValue: 0];
    [NSAnimationContext endGrouping];
}

You'll need to modify the above to be abortable, but I'm not going to write all your code for you :-)

Nicholas Riley
I want to be able to force the animation to stop in the middle; is that possible?
Alexsander Akers
Sure, if you use NSAnimationNonblocking or NSAnimationNonblockingThreaded and keep a reference to the NSViewAnimation object somewhere, you can invoke [animation stopAnimation] then put the views back whichever way you want.
Nicholas Riley
Okay, I tried to do that, but it was really choppy. Do you know how to do Rob Keniger's method? That seems to be the simplest.
Alexsander Akers
I notice NSAnimationNonblockingThreaded seems smoother than NSAnimationNonblocking (makes sense since it isn't runloop based). If it's still slow with NSAnimationNonblockingThreaded, what size image are you trying to animate?
Nicholas Riley
Do you know how to do Rob Keniger's method?
Alexsander Akers
Well, sure, but you could also try a hybrid method. I'll update my solution in a second.
Nicholas Riley
Thanks so much!
Alexsander Akers
+3  A: 

You could implement your own custom view that uses a Core Animation CALayer to store the image. When you set the contents property of the layer, the image will automatically smoothly animate from the old image to the new one.

Rob Keniger
That sounds good. How do I do that, though?
Alexsander Akers