Friday, January 12, 2007

Fun With Custom Keys

I was playing around with NSViewAnimation and I wondered why there weren't more predefined dictionary keys. Then I realized that it was just an associative array*, and I could define as many keys as I want, and take the appropriate action in a delegate method. This is probably obvious to most of you, but I just discovered it. So I gotta, you know, blog it.

Here's an example I threw together that lets you swap one view for another after animatedly resizing a window. The most obvious use of this would be in a preference window, when switching between multiple tabs. For the example names, I'm using Wolf Rentzsch's 'NSX' naming convention.

// Define custom objects and keys
#define NSXViewAnimationSwapAtEndEffect @"NSXViewAnimationSwapAtEndEffect"
#define NSXViewAnimationSwapOldKey @"NSXViewAnimationSwapOldKey"
#define NSXViewAnimationSwapNewKey @"NSXViewAnimationSwapNewKey"

Then somewhere in your controller class:

    NSRect  newWindowFrame  = someCalculatedRect;

// Build a dictionary...
NSMutableDictionary* windowDict =
[NSMutableDictionary dictionaryWithCapacity: 5];

// ...with standard keys...
[windowDict setObject: myWindow
forKey: NSViewAnimationTargetKey];
[windowDict setObject: [NSValue valueWithRect: newWindowFrame]
forKey: NSViewAnimationEndFrameKey];

// ...and custom keys.
[windowDict setObject: NSXViewAnimationSwapAtEndEffect
forKey: NSViewAnimationEffectKey];
[windowDict setObject: someViewToHide
forKey: NSXViewAnimationSwapOldKey];
[windowDict setObject: someViewToShow
forKey: NSXViewAnimationSwapNewKey];

// Create the animation.
NSViewAnimation* windowAnim = [[NSViewAnimation alloc]
initWithViewAnimations: [NSArray arrayWithObjects:
windowDict, nil]];

// Go.
[windowAnim setDelegate: self];
[windowAnim startAnimation];
[windowAnim autorelease];

And finally, the delegate method:

- (void)animationDidEnd: (NSAnimation*)animation
// Standard sanity checks.
if (![animation isKindOfClass: [NSViewAnimation class]])

NSArray* animatedViews =
[(NSViewAnimation*)animation viewAnimations];

if (!animatedViews)

// Grab the window from the first animation.
NSWindow* animatingWindow = [[animatedViews objectAtIndex: 0]
objectForKey: NSViewAnimationTargetKey];

if (animatingWindow != myWindow)

// Iterate through possibly multiple animations.
UInt32 i;
UInt32 numAnimations = [animatedViews count];
id animObject = nil;
id keyObject = nil;

for (i = 0; i < numAnimations; i++)
animObject = [animatedViews objectAtIndex: i];

if (!animObject)

// Grab our custom effect key.
keyObject = [animObject objectForKey: NSViewAnimationEffectKey];

if (!keyObject)

// Execute the custom effect.
if ([keyObject isEqualToString: NSXViewAnimationSwapAtEndEffect])
NSView* oldView = [animObject
objectForKey: NSXViewAnimationSwapOldKey];
NSView* newView = [animObject
objectForKey: NSXViewAnimationSwapNewKey];

if (oldView)
[oldView setHidden: YES];

if (newView)
[newView setHidden: NO];

A trivial example, no doubt, but there are countless possibilities. Imagine calling setDuration: from the animation:didReachProgressMark: delegate to implement some crazy animation curve. Or overriding setCurrentProgress: in a subclass to link several animations more intuitively than with startWhenAnimation:reachesProgress:. Of course, all this is possible without using custom dictionary keys, but this way is a lot more fun.

I'd love to hear any more uses you can think of, and other classes that use dictionaries in a similar way.

*I originally said 'it was just xml', and Peter Hosey pointed out that while the key/object relationship evoked feelings of xml in me, it's not really xml until it's saved to disk. I've updated the text to more correctly describe data in memory.


Peter Hosey said...

Technically, it's only XML if you write it out as such. In memory, it's just an associative array.

Blake said...

ah, thanks for the correction, I'll update the text.

karsten said...

It's not even xml if you write it out as a bplist, which most of the apps do nowadays... you'll need plist-editor to edit these, though.

just my 2ct ;-)

nice read!! really interesting

Kam Dahlin said...

- great tips - thanks!

Quick question, how are you doing your formated code with blogger?

Blake said...

I imagine there's a better way using CSS, but what I did for this post was copy it into BBEdit, detab everything, then replace all spaces with &nbsp; It's ugly but it worked I guess. I also surrounded the code parts with <font face="Monaco" size=1>.

Kam Dahlin said...


I will give that a try for the short and dirty and look for something else for the long term.