Apple’s Keynote has a cool and efficient UI paradigm on application start-up. It displays a panel with a collection of themes from which to start a new presentation, but also has a button on the panel to choose an existing presentation. Pressing the choose existing presentation button performs a panel flip animation to reveal the common system open panel. Canceling from the open panel results in a flip animation back to the theme chooser panel.

Start-up screens are ubiquitous, but I haven’t seen the addition of the open existing button before. It’s a small thing, but it saves the user the annoyance of having to hit cancel and then File/Open. The flip has also got that cool gratuitous Apple bling thing going on.
I’d implemented the flip effect using Core Animation previously in the context of a small item embedded in a larger super view, but that was flipping CALayers, not NSWindows. Also, we’re talking about messing with modal windows to boot, including one that isn’t real sub-class friendly: NSOpenPanel.
A quick consultation with Google turned up two different existing implementations of flipping NSWindows:
- WindowFlipper by Uli Kusterer in 2005
- Flipr by Rainer Brockerhoff in 2006
Both look well implemented and are conveniently packaged up as a category on NSWindow. Both seemed to be showing their age a little though as they chug based on their image manipulation technology. It also wasn’t immediately obvious how to get either of them to work easily with NSOpenPanel.
I decided to take a (very) similar approach to the existing implementations, but to use Core Animation to do the flipping for me and to tweak the setup and tear down to work better with the modal system panels. The key idea I took away from the previous implementations was creating an over-sized, non-opaque window in which a screen shot of the window being flipped would be animated.
The code for creating an over-sized non-opaque window is very straightforward:
NSRect overSizedBounds = NSInsetRect([windowToFlip frame], -100, -100); _flipWindow = [[NSWindow alloc] initWithContentRect:overSizedBounds styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO]; [_flipWindow setOpaque:NO]; [_flipWindow setHasShadow:NO];
Taking a screen shot of the existing window also turns out to be pretty easy:
NSView* view = [[[self window] contentView] superview];
NSRect bounds = [view bounds];
NSBitmapImageRep* bitmap =
[view bitmapImageRepForCachingDisplayInRect:bounds];
[view cacheDisplayInRect:bounds
toBitmapImageRep:bitmap];
Displaying the screen shot in the over-sized window using Core Animation is neat and clean. I used setWantsLayer to request the content view of my over-sized window be layer backed, and then created a new CALayer and added it as a sub-layer to the over-sized window.
The nested CALayer avoids having to mess with layer backing the NSView, which really doesn’t like to be messed much with. There’s probably some room for potential improvement in moving away from a layer backed view. The sub-layer holds the screen shot of the window being flipped, which thanks to the contents property of CALayer and the [NSBitmapImageRep CGImage] convenience method is also dead simple to implement:
NSView* flipView = [[NSView alloc] initWithFrame:bounds]; [_flipWindow setContentView:flipView]; [flipView setAutoresizingMask: (NSViewWidthSizable|NSViewHeightSizable)]; [flipView setWantsLayer:YES]; _flipLayer = [CALayer layer]; [[flipView layer] addSublayer:_flipLayer]; [_flipLayer setFrame:bounds]; [_flipLayer setContents:(id)[bitmap CGImage]]; [_flipLayer setContentsGravity:kCAGravityCenter];
The flipping code is just a CABasicAnimation that animates transform.animation.y from zero to PI/2. The only minor gotcha to remember is to set up perspective in the transform matrix
CATransform3D transform = CATransform3DIdentity; transform.m34 = 1.0 / -850; [_flipLayer setTransform:transform];
What I spent most of my time on was working to eliminate flicker. Initially, I assumed I could just wrap any transitions between the “real” windows and the “fake” over-sized window with calls to NSDisableScreenUpdates() and NSEnableScreenUpdates().
While those calls were definitely a big piece of the puzzle, I also had to be careful to force the initial display of the window ([NSWindow display]) to occur while updates were disabled. I also had to pay attention to the relative window levels as I was dealing with modal sheets. Both of these ideas are present in the two example programs cited earlier, but the modal sheets, timing and granularity of the delegates from NSOpenPanel and CABasicAnimation took some tweaking.
The last problem I encountered in replicating the silky smooth flip of Apple’s Keynote start-up template screen was some hitching around the point of transition due to setup and tear down cost. Again the solution was present in the example programs cited earlier, but I had to futz around with it a little based on Core Animation and modal timings and delegates.
In short, the solution was to use NSPanel's setAlphaValue: to hide the latency of window creation, screen shot creation and the setting of the contents of the CALayer. After the flip is completed from panel “A” to panel “B”, and panel “B” is presented to the user I perform the setup of the overlay window in anticipation of the next flip. This means I take a screen shot of panel “A” (without actually displaying it to the screen) and set the over-sized window up (also without displaying it to the screen), but setting it’s alpha value to zero so it remains invisible until it’s needed.
It’s easy to notice a hang in the middle of the transition animation, but it’s much harder to notice that the app is hanging at the end of the flip animation. In the latter case, the hang “hides” in the time it takes to visually parse the newly presented contents.
Big caveat: I’m targeting Leopard only and it’s not immediately obvious to me which aspects of the approach I took would fail on which previous versions of OS X. Both of the previously cited examples are approaches that work on past versions of the OS and older hardware, and seem like a good way to go if that kind of support is needed.
Hmm, now what important feature was I supposed to be implementing instead of working on eye candy? Flippin’ !#@$% modal panels.

Instead of adding a CALayer sublayer to the CANSViewBackingLayer (or whatever it’s called), you can just replace it wholesale: myView.layer = [CALayer layer];
No more ViewBackingLayer wonkiness!
Also, dude: You need to screencast the result! :D
Oooh! Screencast, good idea! Post updated.
Very cool! :) The animation could probably use some more easing, though.
Thanks! I stopped tweaking when I got close to the timings of Keynote’s “Theme Chooser” panel. I felt guilty for cheating on the rest of my Jira tasks the whole time I was implementing the feature :) Maybe one more timing adjustment won’t hurt. Must…stop…tweaking!
Is the code for this example available somewhere?
Hi Rob, unfortunately not yet. I’ll try to carve out some time to slice out and clean up some example source the coming weeks.
On the bright side though, the source code in two links I mention in the post above will get you 90% of the way there.
The remaining 10% is pretty stock Core Animation techniques (which I learned from the Apple docs, blog posts and the Pragmatic Programmer book on Core Animation) and a dash of fiddly delegate work.
I’ve come across the same requirement in my program and found a somewhat simpler solution to animating the flip. What I do is create two layers, each sized to their respective destination window size, with the destination window layer rotated about y by PI. Both layers are set to doubleSided = NO (which culls the layers when they face away from the viewer).
I then animate the front from 0 to PI and the back from PI to 2PI. The animations complete in a single step, and with a delegate set on animation end for the back animation (arbitrarily – they both end at the same time), I set alpha for the destination window to 1 (before the animation starts I set it to 0 and order it to front).
Animation is hitchless, without any complicated handling of layers and visibility at the PI/2 point…
-Mark
Hi Mark,
Good point!
I’ve use back face culling of layers like that before and I can’t remember why I didn’t take that approach in this case. Probably for no good reason :) I agree it definitely seems like a simpler approach than messing around with hide / showing layers at the flip point.
If I recall, the main fiddly bits were really having to do with avoiding flicker when displaying the flippy overlay window over-top of the modal dialog, then hiding the modal dialog underneath.
The hitching issues I ran into didn’t have to do so much with hide / show issues at the flip, but rather where to “take the hit” of the screen-shot process. As I mentioned in the original article, the key was to do it after the flip in anticipation of the next flip, rather than just before or during the middle of the flip.
Thanks for the comment!
Hi Jeremy,
Could you post your code? I’m trying to animate a NSWindow as well and would like to see how you did it. Don’t care if your code is a mess, just need to take a look at it :)
Thanks!
Hey Jeremy,
you did a very good job with these flippin’ modal panels.
Unfortunately, my knowledge about Core Animation isn’t that much so I cannot create a working project from the code mentioned in this article.
Is there a chance you can get some time and put the project on your website?
Thanks so much in advance!
Anthony
I’ve packaged up the flip effect code that I came up with and posted to the (under construction) website of mine :)
You can find the code at:
http://www.lorem.ca/samplecode/LIFlipEffect.zip
Mark: The link to your code is broken. Could you fix this?
Thanks
Zatman
https://github.com/monyschuk/LIFlipEffect
A really good answer, full of rtainoality!
EkJNOz fwowoknnyjso
vXM2Wz fdlvuyuzdhvu
You probably already know this, but it wasn’t written in the post. So for the benefit of those who are unaware:
In Keynote hold the SHIFT key when pressing the Open an Existing File… button on the Theme Chooser window to see the Apple animation at a slower speed. Also works for the Cancel button on the subsequent Open panel. In fact many OS X animations will slow when activated with SHIFT held down.
The link to your code is broken. Can you fix this?
Thanks
Zat
I’ve been smashing my head against the wall with this, but how do you avoid the flicker when animating the NSOpenPanel back to the start window? I’ve tried everything, but I can’t seem to get it to let me capture the cancel click before the open panel is actually closed.
I’ve tried:
- using NSWindow delegate methods on the open panel (apparently, none of the NSWindow delegate methods work)
- monitoring panel:userEnteredFilename:confirmed: (not called)
- showing the dialog with a callback (callback happens AFTER the panel disappears)
and none of these work. I’m getting really frustrated and am hoping someone reading this can help me out! Thanks in advance!
I just figured this out today. You need to disable the screen updates. As mentioned in the Blob, it’s found in the Flickr app. It goes something like this:
//do the screen disabling/enabling to prevent flicker
NSDisableScreenUpdates();
//make the final window visible
[endWindow orderWindow:NSWindowAbove relativeTo:[_flipWindow windowNumber]];
//order the animating window out
[_flipWindow orderOut:self];
//fixup stuff on the final window
[endWindow setAlphaValue:1.0];
if (isKey) {
[endWindow makeKeyWindow];
}
[endWindow display];
[_flipWindow release];
NSEnableScreenUpdates();
er, that was the Flipr app…
My question is when do you disable screen updates when the start window is an NSOpenPanel? I haven’t been able to disable updates before the cancel button removes the panel from the screen leading to bad flicker at the beginning of the animation.