Application Technologies • 1:00:24
With the introduction of Core Animation in Leopard, Mac OS X provides an exciting new model for implementing high-performance animations and visual effects. Cocoa's new animation capabilities are designed to leverage this powerful new facility and its intuitive animation model to maximum advantage. Learn how you can use the new enhancements in NSView and other classes to easily add compelling animations and visual effects to your Cocoa user interfaces in this demo-intensive session.
Speaker: Troy Stephens
Unlisted on Apple Developer site
Transcript
This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.
Wow, thanks for coming. You guys do know there's a Core Imaging session right now, don't you? Hi, welcome to session 132, Cocoa Animation Techniques. My name is Troy Stephens. I'm a software engineer on the AppKit team. And today, I get to tell you about some really neat stuff that we've been working on for Leopard, with the goal of making it very, very easy for you to add sophisticated animations and visual effects to your Cocoa applications, to your existing view-based user interfaces. So, animation can be a lot of fun, adding animation to your apps, and you can and will go completely nuts with this stuff. It can also, however, serve some useful user interface purposes and provide good user experience benefits.
And that's been part of the motivation behind our developing this technology and making it easier for you to do these kinds of things. Animation can make your application feel more adaptive, live, dynamic, responsive. And it can put that kind of last bit of polish, that extra fit and finish on your app, that tells users that you've paid attention to every detail, and that you really want them to have a highly interactive user experience. But in addition to stuff like that, that's just sort of endears users to your apps in a general way, you can do things like help users maintain context.
In Panther, we added animated scrolling, for example, for web views and mail messages, and now you may notice in your Leopard seat, it's turned on for all scroll views. So, you know, when I'm reading a web page, if the page jumps when I page down, I kind of got to figure out where I was and pick up, but with animated scrolling, my eyes can kind of track along as the page is moving, and I can pick up where I left off much more easily and continue reading.
Another use for animations is to alert users to state changes that they themselves did not necessarily initiate. For example, the iChat buddy list is one of the first places we really saw this, where, you know, you have buddies coming online, going offline, and you kind of want to get a visual cue. That something's going on there, you know, "Oh, dad's online now," or something, or somebody just left, but you don't want to be interrupted with it.
It's something you can be sort of peripherally aware of, and animation works beautifully in that particular situation to sort of give you a cue that something's going on off to the side. Another thing you can do with animation is indicate sort of a tentative response to a user action that hasn't been committed yet.
An example of this would be when you go to customize your toolbar, and you're dragging a new item in or taking an existing item and moving it around, you'll notice that all the other toolbar items, sort of move around in a live animated way to show, you know, "Okay, this is how we're going to move out of the way to make room for the item that you're about to drop." So if you drop it, you know where it's going to go. You can do that kind of thing arguably without animation, but it just looks a lot slicker and feels a lot more responsive when animation is used. Lastly, another thing you might want to do is indicate that some major change of interaction mode is occurring.
Like front row, for example. If you take out your Apple remote, and you click the menu button on it, then everybody else who's seated in your section will see their desktop slowly fade away, and the notes they're taking will fade away as the front row UI comes on.
And it gives users a cue that, "Okay, we're leaving the desktop now. We're leaving that set of metaphors and that means of user interaction, and things are going to work a little differently. We're going to interact with the machine, with the remote, and so forth." So these are just a few examples to give you some ideas of the kinds of things that you might be able to find uses for animation for in your own applications.
So today we're going to look at some new animation support that we've been working on for AppKit. We'll start off with a demo to sort of seed a picture in your minds of just what kind of stuff we're talking about being able to do. Then I'll go into a design overview of sort of the hows and whys of how we decided to present this functionality.
And we'll look at the new API in detail so that you'll be able to go away and start using this stuff right away on the Leopard Seed. And I'll close with some usage tips and performance tips for how to get the most out of this. So if we could go to demo D please. We'll start with our first demo.
And I know you guys have seen a lot of sort of image browser apps this week. So I apologize. I have another image browser app to show you. But I made this one look a little different. So in this one, the user's presented with a set of images as slides.
And each of these-- this is all done using Cocoa Views. Each slide is actually a small subtree of views. I've got the custom view that draws the slide carrier, the sort of white, rounded corner, bezier path, using a subtle NSGradient there. NSGradient is a handy new class that's been added to AppKit. So yeah, it's much easier to do that now. No need to drop down to Core graphics and write lots of C code.
And in fact, the background here is a very subtle gradient from dark gray to black up at the top. So we've got a simple container view that just sort of draws the slide shape. We've got a-- up here at the top of each one, we've got a simple button that is a checkbox style button that both displays the title and enables me to check off a slide as selected. And then we've got a couple of ordinary NSText fields down at the bottom that display some information about the image, what type it is, and what its pixel dimensions are.
So far, this is all pretty ordinary Cocoa Views. And I've added some sort of different layouts, different ways we can sort of scatter the slides around. We can arrange them in sort of an infinite loop configuration, arrange them in a circle. And this is just sort of setting views. All I'm doing in each of these cases is computing the frame origin that I want and the frame rotation for each of the views and putting them in the new configuration. So so far, this is all ordinary Cocoa Views stuff.
But what's interesting here is that these views are not being composited into the window via the usual means. Behind the scenes, AppKit is creating automatically a layer tree that mirrors everything in the view subtree here from the content view here all the way on down the view hierarchy to the leaves. So we've got views and layers. And the significance of having view content in layers is that each view gets its own layer. Each layer has its own backing store, therefore you have pre-cached view content.
And it's very easy-- once you have pre-rendered content, it's very easy to move stuff around efficiently. And if we want to animate stuff, I can give this a non-zero duration here. And instead of sending things immediately to their new locations-- let's say I want to relay these out-- I can just sort of have them smoothly animate to their new position.
And one of the take points here-- And I'll just let that run automatically. So I'm just cycling among the different configurations. And this is running very slowly, so you can get a clear picture of it. But this can run fast if you want. You want half-second animations. You can make people a little seasick. There we go.
[Transcript missing]
So, you know, you want to pick a transition and that just works and it's very little code. Part of the take-home point is this is very little code. And this is all running on the very same leopard seed that you guys have received this week that you have in your possession. So this is all stuff that you can begin experimenting with today. So if we could go back to the slides, I'd like to get into how you can do that.
So a quick design overview of what we've decided to do. You may have had the chance to attend the Core Animation session yesterday. If not, I'll give a quick recap of Core Animation. Core Animation is a powerful new framework and foundation for compositing and animating of content. Core Animation is an Objective-C API. It's part of the Quartz Core framework. Its development code name was LayerKit, so you'll hear me say LayerKit occasionally by mistake. And that's why all the classes have the LK prefix in the names.
The most basic unit of currency that you work with in Core Animation is layers. Layers are in many ways analogous to views. The layer API was patterned after the view API. And layers are like these sort of blank canvases that you can put whatever content you want into. They are nestable in a hierarchy.
Each has its own local coordinate system defined in terms of its parent's coordinate system. There's some limited support for event handling and that sort of thing. So it's very much like working with a view hierarchy, except you don't have this implementation of all the Aqua controls. Basically, layers are just like an NSView.
They don't have any inherent behavior, but they have sort of potential behaviors and a way that they work together. As I mentioned, you have per-layer content buffering, so you can do very efficient kinds of animations and applying effects and that sort of thing. That's very important. And it's part of how Core Animation provides all these performance improvements over standard view animation.
And it has this fantastic implicit asynchronous animation model where you basically set new properties on things and it's implied that you get an animation to the end of the animation. So you can change the new target state rather than the changes taking effect immediately. So that's a really powerful, easy way for you to implement animations to say, I want something to animate. You just change properties.
Core Animation comes with a very expressive set of animation description classes that we'll look at in detail that let you specify all kinds of different animations. It encapsulates a set of layout algorithms that you can use as layout managers to specify how sublayers are positioned when their super layer changes size. And it provides you access to the full-duty animation. You can have a full range of core image effects, shadow effects on layers. You can have masks on layers and so forth.
And one of the greatest potentials that Core Animation has going forward is to be sort of a unifying graphics technology that helps bring together Quartz rendering with other sort of sub-universes of rendering like GL rendering, QuickTime, Quartz Composer content rendering that previously have had a really hard time coexisting together.
You know, if you have some GL rendering in a window, that's usually going to a hardware accelerator. You know, if you have some GL rendering in a window, that's usually going to a hardware accelerator. And it doesn't play well with having controls on top of it and stuff like that.
So stuff that has been really hard to do before is going to be much easier to do in the future. On the other hand, there are a number of things that Core Animation does not attempt to provide. Core Animation sits in Quartz Core down below the AppKit layer.
And among other things, it is not an implementation of the Aqua Control set. It's not a user interface toolkit. It is an Objective-C API, so in that sense it's somewhat object-oriented and high-level. But it does not... It does not attempt to supplant AppKit or do the job of providing all these standard controls for you.
Likewise, you get no built-in accessibility support with it. Other things like printing support, you can, I found out, you can, I found out in the Core Animation session yesterday, render a layer tree to a PDF, so there is support for output to things other than the screen. But AppKit has this whole complex printing architecture that provides you with a lot more when it comes to printing.
Complex event handling. There are some basic event handling capabilities in Core Animation. But AppKit has all these long-standing paradigms for things like input focus, the responder chain, that give you lots of power when it comes to event handling and lots of flexibility. So that's another thing AppKit provides. Drag and drop support, support for things like tooltips, cursor recs, tracking recs. These are all just sort of higher-level things that you expect more from a user interface toolkit like AppKit.
So we've got a lot of power down in Core Animation. And we want to make it really easy to use. And so the thing we wanted to do going forward was enable you to get the best of both worlds. And so what we've decided to do in AppKit, our goals were, first, make animation easy and fun.
So try to minimize the syntax, make this really easy to take advantage of. Leverage Core Animation's performance and feature benefits as much as possible. And extend the same sort of convenient and powerful compositing and animation model to you for use with views. So that you can continue to work with views and not even worry about layers unless you really want to. So that you can continue to work with views and not even worry about layers unless you really want to. or need to.
In hand with doing that, we've added a set of new visual effect properties to NSView, like the shadows I demonstrated, like the ability to apply core image filters to the content of a view that it's rendered. All of that stuff was inspired by the new properties on layers that we didn't have in Views, so we said, okay, well, let's bring this up to the View level and use Core Animation to do all of the rendering.
Likewise, AppKit will manage all the layer creation and refresh for you automatically. There is, apart from one line of code in that demo where I throw the master switch and say, OK, I want everything in this view subtree to be rendered and animated using LayerKit, there's not one other line of code in that app that has to worry about the existence of layers. This is all happening automatically. AppKit takes care of it. When a view needs redisplay, the view gets redrawn into its layer and so forth. And AppKit manages the layers for you.
Troy Stephens However, if you do want to start meddling with layers, if you want access to properties that layers have that we maybe haven't mirrored up to the AppKit API, some of the specialized stuff like 3D transforms like you saw in the State of the Union demo and the building, the city of album art, that kind of stuff, you can get out the layers if you want to. And you can even replace them with your own layers if for some reason you wanted to do that.
Likewise, if you just want to work with layer trees in some particular part of your window and you just want to build your own layer tree and have us render it, you can do that. You just set up a view to render a layer tree and you say, "Here's your root layer and we'll do that for you." So here's how mirroring of view content into layers works, just to sort of give us a mental picture.
I've got my slide, which is really sort of a subtree of views that's broken down into the slide carrier view. And then I've got a button in there. I've got an image view, and I've actually got a couple of text fields in the example. So we've got this view subtree to draw, and AppKit sees this and sees that it's in a descendant of a view that's switched on for Core Animation-backed rendering. So AppKit goes ahead and creates when it's time to render.
We create a layer for each view automatically, and the layers start out empty. We size them to match the views. And when it's time to render, we say, okay, we've got to push the content into the layers. So automatically, the views get their content rendered into the layers. As far as the views are concerned, they don't have to do anything special.
Their draw rect gets called when it's time to draw. The view just draws as usual, except that AppKit behind the scenes has diverted that drawing into the layers graphics context so that that drawing can be individually buffered and cached for later use. And then the layer tree, when it's time to draw the window, we composite the layer tree using Core Animation.
So how do you switch this on? This is neat. How do you turn it on? We want to make this real easy. So you choose a root view, and you can do this for more than one view in a window. And you just say, set wants layer, yes. And that's how you throw the master switch. And then AppKit takes care of the rest after that.
So when you do this for a view, it has the following consequences and implications. AppKit mirrors the view subtree automatically into a layer tree for you. So we'll create the layer tree. We'll update it as needed and so on. When you change properties of the views, we'll change the corresponding layer properties. That all happens automatic. Views draw into their layers via drawRect as before.
When you do setNeedsDisplay for a view, that carries over to the view's corresponding layer. So OK, you've changed some of your view content. Your model has changed, and you need to redraw some of the view. OK, so I need to do setNeedsDisplay in this rect or setNeedsDisplay for the whole view. We note that, OK, we need to do the same for the layer. And so when we redraw the view, we'll refresh the layer content.
View property changes map to layer properties automatically. AppKit implements animation of non-layer properties. So this is something that we wanted to add. We wanted you to be able to animate more than just the core geometric properties that you get for layers and enable you to animate just about anything you want, including your own properties.
And we'll see how to do that shortly. Finally, any setting of this flag further down in the View subtree will be ignored. So we look for the root-most view in an ancestor chain that has once layer enabled. And that will be the view from which we start doing layer tree rendering all the way down.
I mentioned some new view properties that we've added, including visual properties. Every NSView now has an alpha value. This is named to match the NSWindow property, and it works exactly the same. Once the view has drawn its content, this is the overall opacity with which we will composite the view into the window. You could set it to zero and effectively make a view invisible if you want. You can vary it, and you can animate it.
Likewise, views have filters. You have the ability to specify any core image filters that you want for the view's content, for the background behind the view, and for-- actually, you can specify any of the core image compositing filters if you don't want source over compositing. Most of the time, you're not going to be interested in that, but the content filters and background filters are more interesting, because those are arrays of filters. Those are array-typed values.
So you can actually specify maybe you want to sepia tone and then blur and then do some other halo effect. You can do that. You just specify an array of three filters, and boom, off you go. And we automatically-- AppKit or actually Core Animation under the hood will link those filters together for you and run your content through them. You can specify a per-view shadow, much as you can specify a per-layer shadow. And at the AppKit level, since we already had NSShadow as a convenient encapsulation for expressing shadows, we used that as the type.
Also, we provided some new API for animation control. You can-- each view has an animations dictionary, and each window also has an animations dictionary that you can use to specify per instance what kinds of animations you want for different properties. And we'll look at that in a bit later. And then finally, the view's use of Core Animation for rendering and for animating is controlled by that wants layer flag. And then you also have the ability to get and potentially set the layer. So there's a layer getter, and there's a set layer method also.
So we're starting to get into API inevitably, so let's just keep going. So what we'll look at in this session is how to animate your views in Windows using the new API, primarily focusing actually on views. We'll look at how to initiate animations. We'll look at view and window properties that animate for you automatically by default.
So you just have to specify that you want animation and you get some default parameters. And we'll look at making your own views custom properties animatable, which you can do. That was very important for us to extend that to you so you can do it with arbitrary properties. of your own design.
We'll look at building animation specifications of various kinds for, I want a transition of this particular type. Or I want something to animate from A to B, but I want a particular type of time curve on it. So we'll look at how you express the kind of animation you want. And finally, we'll look at applying filter effects to view content and how that works.
So when we went to design this, we went back to basics. And we said, well, what fundamentally is animation? What does it mean to animate something? Objects have properties, obviously. Animation, when you think about it, is really nothing more than varying a property of an object over time, rather than setting that value immediately. So when you set a target value for a property, you should be able to just say, OK, I want this to go to there. And it's here right now.
And go ahead and initiate an animation. It should be really easy to do that. So we provided default animation parameters for all of the built-in view and window properties that you might want to animate. And so animations just sort of work when you say, OK, animate to here. There are opportunities given to you to substitute different parameters if you want different kind of animation to happen, different kind of timing.
And any animation, as you saw in the demo, that any new animation instructions you give an object automatically supersede any in-flight animations for the object property. We'll just start from where we are now and move on to the new property. So how do you set a target value? How do you do this? We spent a lot of time thinking about what the API should look like for this. Setting an immediate value, like setting the frame of a view, just looks like view, set frame, rec. That's really simple. And we wanted to keep it as close as we could to that simplicity without adding a lot of syntax.
So we thought about, well, you know, we've got NSWindows set frame animate method. And so we could add an animate with a bool parameter to each of the setters. But then that becomes an explosion of methods, and that requires us to rethink KVC. We didn't really want to do that. So we said, well, how about if we, what we ended up coming up with is this concept of an animator. So views and windows have animator objects. You can ask a view for its animator. You can ask a window for its animator.
And then all you have to do is message the animator instead of the view or window. So you insert a little extra syntax. You say view animator set frame. And that's all you have to do to move a frame from A to B and have it animate automatically.
And this goes for a lot of other properties, not just frame, frame rotation. You want to set the alpha value and have that fade to a new value. You can do that, too. So what exactly is an animator? An animator is a proxy in a sense, somewhat similar in concept, at least, to NSProxy. They aren't necessarily NSProxies. It's a proxy for a target object that has animatable properties like a view or a window.
You can treat the proxy as if it was the type of the object that you got it from. So you get a views animator. You can basically treat that as if it was the view itself. You can send it the same kinds of messages the view can respond to. And in fact, you can even pass it to code that expects to get a view.
And that's perfectly valid. And in fact, in the demo there, all of that layout code was written thinking that it's dealing with views. I pass it a view, and I say, layout subview's a view. And I've got a circular layout function, and I've got the infinite loop layout function.
And those just think they're dealing with views directly, and the parameters are typed as NSView. What I'm doing is passing an animator in for the view that's the container view there, and then everything just sort of works. So if you have existing layout code that's written to just interact with views and set their properties, or not even layout code necessarily, but code that sets properties on a whole bunch of views in your UI, you can animate those changes now very easily just by surrounding it with some context.
So when you send value set messages to the proxy, or the animator rather, those can initiate animations. And what happens when you do that, first we search the target object for an animation to use. So the target object in this case would be the view. You get a views animator. The target object is the view. We search the view for, OK, this property is going to animate.
What kind of animation should I use? The property's name is the search key. So if we're changing the frame origin, the string coming in there will be frame origin. And that's what we'll use for the lookup, or if it's frame or alpha value, that sort of thing. AppKit then checks the current NSAnimationContext duration. NSAnimationContext is a brand new class that we've introduced in Leopard. And I'm going to discuss that in the next slide.
If we find an animation and we find that the duration in the surrounding animation context is greater than zero, we initiate an animation to the specified target value. So those are the two things we need. We need to find an animation to use. If we don't find an animation, we assume you don't want the property animated. If we find a zero duration, we assume you don't want the property animated either. You don't want anything animated within that context.
By default though, AppKit provides default animations for all the standard properties like frame that you might want to animate. And likewise, we provide a default context duration of a quarter second, the same as Core Animation. So you don't even have to worry about setting those for a lot of things and you just have to message the animator and that's all you have to do to get things to animate. So if we find an animation, duration is greater than zero, we initiate an animation, the proxy passes everything else through. So that's sort of the general proxy behavior that you expect. Thank you.
So NSAnimationContext, I mentioned, is a new class in Leopard. And you can think of NSAnimationContexts in a sense they're sort of analogous to graphics contexts in that they hold state. And each thread has a stack of these of its own. And there's always a current animation context, just like there's always a current graphics context. And the properties are per context.
Right now, the only property that NSAnimationContext holds is the duration that is used for implied animations. For any animation that doesn't have a duration explicitly specified, we just take the duration from the context. And that makes it real easy to say, okay, I'm going to animate a bunch of stuff. I want a duration of one second. I set that up in the context, and then I just message animators to change the properties. And it just goes, and everything has the same duration. NSAnimationContexts are somewhat analogous to Core Animation's LKTransactions.
Which you will learn about if you study the Core Animation API. Typical usage for an animation context is you begin a grouping, and then you set the duration that you want, and then you do a bunch of animation operations, and you end the grouping. So begin grouping is sort of like pushing an animation context on the stack. You get a fresh context that's a copy of the previous one. So by default, you inherit the duration from the context that was previously active.
And then you can say NSAnimationContext, current context, set duration. And here I'm setting it to a half second. Because I'm going to animate a bunch of stuff. And I want all the animations in that grouping to have a half second duration by default. You can nest these. So you can group things such that they have different durations. And you can stack these up as high as you want.
The two main significant points for NSAnimationContext is that all the animations that you initiate within a grouping have the same inherited implied duration. And they all also implicitly start at the same time. They're guaranteed to start in a synchronized way. So when you end the grouping, that's when we fire off the animation. So it doesn't matter how long it takes you to assemble this in code. We will synchronize all the animations unless they have explicit begin times.
So as I said, this is sort of a general set of concepts. We thought all objects that have properties, you may potentially want to be able to animate those properties. So we defined an NSAnimatablePropertyContainer protocol that's new in Leopard. This is declared at the top of NSAnimation.h. So an animatable property container is anything that has properties that can be animated. And right now in your Leopard seed, views and windows both conform to the NSAnimatablePropertyContainer protocol. And we're considering extending that to other classes potentially in the future. So there are five methods that every animatable property container implements. You can get an animatable property containers animator.
You can get and set a dictionary of animations on a per instance basis. There's also a class method, default animation for key, that you can override to specify on a per class basis what sorts of animations you want to respond to different kinds of property changes for different keys. And then there's animation for key, which I'm going to discuss in a moment, which is sort of a top level search mechanism through which AppKit and CoreAnimation find the animation to use.
So here's how the search process works, just briefly. Animation for Key is the top-level method that we invoke. So you've messaged an animator and you've said, you know, view animator, set frame, and we say, okay, they're trying to change the frame through the animator. Let's look for an animation that corresponds to frame. So we'll invoke Animation for Key. The key name will be frame. And we'll look in the object's animations dictionary. So per instance animation settings override per class settings, as you might expect.
Troy Stephens If we don't find one there, we'll check the object's class's default animation for key method and see if the class has specified an animation for the frame key or whatever the property key may be. So again, Animation for Key first checks the instance dictionary and then calls default animation for key.
Troy Stephens To make it so that you don't have to be specifying these all the time explicitly, for all of the default view and window properties, we've specified default simple animations of interpolation from the object to the key. Troy Stephens So we've got a lot of different options here. We've got the default animation for key, and we've got the default animation for key.
Troy Stephens So we've got the default animation for key, and we've got the default animation for key. Troy Stephens So we've got the default animation for key, and we've got the default animation for key. target from initial position to target. So views frame origin, frame size, frame rotation, all the frame parameters are animatable, all the bounds parameters.
So all the geometric stuff has default animation specified for you. You don't have to specify them. Same for alpha value. All the filter properties for when subviews change, by default, you get a fade animation. And there's also this nsanimation trigger order in and out set of constants that you can pass in that are defined in nsanimation.h. And that's analogous to adding a view to its super view or removing the view. So you can specify, if you want to specify a transition for when the view is added or when the view is removed, you can do that sort of thing.
For nswindow, currently alpha value and frame have default animation specified for them. But as I said, we didn't want to limit you to just animating the built-in kind of stuff that we provide in the app kit, all the standard properties of views in Windows. So you can enable animation of your own properties that you define in your own view subclasses.
The way to do this, you can specify-- again, you specify an animation either on a per instance basis or more commonly, you'll want to do it on a per class basis. In either case, you're mapping property name keys to LK animations. AppKit knows how to interpolate some of the basic scalar types, so floats, doubles, CG floats, NS points, NS sizes, NS recs, all the geometric aggregates of those automatically. So for all those types of properties, anything that you have that's of one of those types is potentially animatable. And there are a couple of steps to doing this.
First, you want to have a KVC compliant setter method that the animator proxy can find. And so let's say I have a class, myView, that has a border width property that defines how thick the border that the view draws is. And it's really important that I be able to animate this because that enhances the user experience. So I have a set border width.
At minimum, I need a KVC compliant setter method, set border width here, that gets the new value, stores it, and then starts the view as needing redisplay in whatever areas are affected by that. So you should be doing this. You need this already. So really, there is no step one here.
Step two is to do something like specify an animation, either by overriding default animation for key or setting it in the per instance dictionary. Normally, you're writing a new view class. And if you want a property to just animate by default, override default animation for key. And then people don't have to individually configure their instances. And if they want to override and replace the animation that you specified, that's fine. They can do that too. But then they have a default to fall back to.
So here, I've done this for border width. I've overridden default animation for key for my view subclass. I'm looking for the border width key to be coming in. And if I see it, I'm returning an LK basic animation. And that is one of four concrete animation classes that Core Animation provides. We're going to look at those in detail to see what kinds of animations you can specify.
This is just saying, this is the most basic kind of animation that there is, as its name suggests. It's an interpolation with a standard curve. And the duration is implied, and so forth. So I'm saying, basically, all I'm saying is I want border width to animate. And then, any time you override this method, you want to invoke supers implementation for any keys you don't recognize. So we're doing that here at the bottom.
So what kinds of animations can we describe? We thought about this for a while. And Core Animation has a very rich set of expressive classes for describing animations. We thought about, well, should we wrap this in the AppKit API? And it turned out there was really no clear benefit to doing so. So what we've done is we've gone ahead and used the LKAnimation classes directly in the AppKit API. So you just create LKAnimations. And these are really actually very simple classes.
They're very suitable for that because they're basically massive containers for properties, properties that have implied default values. So at the root, you have LKAnimation, which defines the basic animation-- the basic properties that all animations have. And animation can have a timing function, which basically gives you the opportunity to specify some nonlinear progress curve that the animation will follow. And animation can also have a delegate.
So if you're interested in knowing when the animation starts and when the animation stops, you can get callbacks, objc messages, to your delegate by wiring the delegate up. From LKAnimation is derived LKPropertyAnimation, which is still a semi-abstract class. It's the root class for animations that animate properties, things that have identifiable names. So a property animation has a key path that specifies the property to be animated.
Normally, you just leave the key path empty. You just leave it to be implied. Because when you message, say, a view through its animator to set its frame, well, we know that frame is the key. And we'll fill that in for you. But what this lets you do is if you explicitly set the key path, you can specify that something else is to be animated when that change fires for that property. So OK, when subviews changes, maybe I want my frame to animate.
And you can use an explicit key path setting to do that kind of thing. You can specify whether animations are additive or cumulative. That's something that's mainly used by core animation at this point. And then so if we want a concrete animation, there's LKBasicAnimation. This is the most common kind you're going to use. It's derived from LKPropertyAnimation.
Basic animation has a from value and a to value and an optional by value. And normally, we just let this all be implied. You're setting the to value when you message the animator to set the target value for the property. The from value we get from where the object is right now and likewise for core animation. So if you want to get a little more complicated than that, there's LKKeyframeAnimation, which lets you specify basically a series of waypoints so that you can have something animate from A to B to C to D, either geometrically. Or in terms of any other property that's animatable.
So a keyframe animation has a set of waypoint values, a set of key times, a path that it can follow potentially. You can specify an array of timing functions now instead of a single overall timing function and a calculation mode. So those are sort of the two animation types for most properties.
But you also want to be able to have things like transitions that you can express. So there's the LKTransition class, which is derived from LKAnimation. A transition has a type, which sort of expresses the overall kind of transition you want. There are four types defined by layer kit, rather by core animation.
The subtype expresses basically the direction of the transition. So you want something to slide from left to right or top to bottom, similar to the way you pick things in Keynote. You pick the overall transition and then set parameters. You can set start and end progress if you want. But normally those are just implied.
And you can set a filter. Like I said, you can take any core image transition filter, which is one of a set of-- one of a class of filters that QuartzCore provides, and say, hey, I want you to use this filter and do a ripple transition or something.
When you specify that filter property, that overrides the type and subtype, and those are ignored. So there's one other class, LKAnimationGroup. And that's basically like an array of animations. That gives you the opportunity to, instead of specifying one animation, maybe you want to aggregate a whole bunch of them together. So you can do that with LKAnimationGroup. It just has one property.
It's the array of animations, and that lets you define-- potentially complex compound animations that have a whole bunch of things in flight all at once. So it looks like a lot of complexity here, a lot of stuff. But really, there are only four concrete classes that you need to be concerned with. And a lot of the time, you're just going to be wanting a basic animation or a transition. And then maybe you start to get more complex with the groups and keyframe animations.
So, much like comedy, animation is all about timing. And up here you may have noticed the LK timing protocol that all LK animations conform to. And we'll look at that in detail because those timing properties are very important for defining how animations behave. Troy Stephens So, most basic property of all, duration. How long do you want the animation to execute for? It's just an interval in time value in seconds. The default value is zero, which implies that we're going to get the duration from the surrounding NS animation context.
Or if you're using Core Animation directly, you're going to get the duration from the surrounding transaction. Troy Stephens Likewise, there's a begin time that's implied to be, well, whenever I close the animation context. That's when I want you to begin it, whenever I'm initiating the animation. Troy Stephens But you can specify a begin time in the future so that you can explicitly get the duration.
Troy Stephens So, you can set an animation to auto-reverse, so that gives you an easy way to go from A to B and back to A. And you can potentially repeat that animation. And that also works together with the auto-reverse property. Troy Stephens So, you can set, there are a couple of ways you can repeat.
You can either set a repeat count, so I want this to repeat seven times. Or you can specify a repeat duration, which is how long overall do I want this animation to run for. Troy Stephens You can specify a time offset and a speed. And this is what I want to do. Troy Stephens And these are parameters that are used primarily by core animation right now because you have the concept of nestable, just the same as you have local space coordinate systems for layers.
Troy Stephens Each layer can have its own local time coordinate system. And this is also significant for LK animation groups, which is why it matters for AppKit based animations. Troy Stephens Because you can define a group to have its own local time coordinate system that's convenient for you to work with. You can scale time. You can specify an offset. Troy Stephens There's also finally a fill mode parameter.
Troy Stephens That defines basically how things end up when the animation terminates. Does it stay in the layer tree, in the render tree or not? That's mainly used by core animation. We don't really touch that at the AppKit level, but it's there. Troy Stephens So, as I mentioned, you can define local time coordinate systems within an animation group. And this is just a simple linear equation.
Normally your speed is one and your time offset is zero. Troy Stephens So really it's just sort of an offset calculation to get down into the animation group. So you can define an animation group so that your time is zero. Troy Stephens And you don't have to worry about what the surrounding absolute time is. Troy Stephens So just some handy stuff. Most of the time you're just interested in duration and begin time.
So how do we create these instances, these basic animations? We saw how you just create a basic animation with default parameters, LKBasicAnimation, animation. It's that simple. And animation, by the way, is the defined factory method for all classes of LKAnimation. So it doesn't matter if you're creating an LKTransition or an LKBasicAnimation, you just send it an animation message. This creates you an auto-released instance. Say you want to then take that basic animation, set a duration of one second. Real simple, set duration 1.0. You want to add a delegate, anim, set delegate, and specify your delegate object.
Creating animation groups is pretty simple. You just instantiate the animation group, LKAnimationGroupAnimation, and then you set the group's animations as an array. And the interesting thing about animation groups is they don't have any implied sequence, really, although an array is an ordered collection. The sequence isn't really important here. You can use an animation group to have animations batched together that execute simultaneously. Like if you give them all the same begin time.
You can have animations that run sequentially. Have anim2 begin after anim1 has terminated. You can set it up that way. You can have animations overlapping within an animation group. You can have time gaps where nothing's happening and you're waiting for the next animation to start. So you can define sort of the timeline of an animation group however you want. So that's a very powerful feature for collecting animations together. Transition animations are easily specified.
Here in the top example, we're creating an LKTransition. We're setting its type to LKTransitionMoveIn. This is one of the four basic built-in transition types that CoreAnimation itself implements. And we're setting the subtype to transition from left. So we want to move in transition from the left. And that's all it takes. It takes three lines of code to instantiate that. If you want a CI filter-based transition, you create an LKTransition again, but then you're instantiating a CI filter using all the normal means of instantiating a CoreImageFilter.
Usually you just go filter with name. You set the parameters of the filter to default. You override any other parameters you want. And then when you're done, you just say animation set filter filter. So you're setting the LKTransitions to use that CI filter. And that's really simple. That's all it takes to do that.
Finally, what if you want to use this to apply filter effects to views, like for example the pointillized filter that I used to highlight my view there? Adding a content filter to a view is really simple. Again, you instantiate the filter and that's most of the work is configuring the filter the way you want it.
Once you have the filter you want, stick it in an array and you can even do this in line because a view, again, can take an array of content filters. It doesn't have to be just one filter. You can specify as many as you want. So if you want to apply that effect immediately, you just say, "View Set Content Filters" filters, pass it in the NSArray of filters and blam, the effect is applied immediately.
Or if you want the effect to transition in over time, you can say, "View Animator Set Content Filters" and then whatever filters, if any, were applied before will sort of transition from the way the view looks now to the way the view is going to look when the new filters are applied.
So again, in our slide example we had highlighting with a pointillized filter. You might want to use something a little more subtle or a little more crazy. But it's that easy. That's all you have to do. View set content filters and you've got core image filtering on your view content. And we don't even have to re-render your view when the filters change. Because again, all of this content is being cached in layers. So this has enabled one of the many great visual effect features that are enabled when you're rendering your views in layer-backed mode.
So finally, some usage tips for this stuff. How can you get the most performance out of this? And also, what kinds of things should you look out for? So there are a few behavioral nuances to look out for. A few things that are a little different from standard view compositing mode.
For one thing, the semantic difference between sending set needs display to a view and doing the same thing for an ancestor for the same sort of area in the window becomes more significant now. It used to be views are all sort of mushed together in the window backing.
But now, we're able to do that in a way that's not so much a problem. So if you dirty a certain area of the window, it doesn't really matter which view you're talking to as long as there's something occupying that area. You can get away with it and the content will all be redrawn to cover the affected area of the window.
That becomes obviously something that might not work very well. Now that you have views, each view has its own backing store. So it's really important that you dirty the stuff that actually needs to redraw its content. Not only so the content will be updated, but also so we don't waste it.
We don't waste time re-rendering content that actually is static and doesn't need to be redrawn. So be aware of that. Another thing to note is that when you're animating layer backed views and you're animating properties such as the geometric properties that we use core animation to implement that animation so it happens completely without your app's intervention.
From your app's perspective, those properties are changing immediately in its own space. So you say view animator set frame blonde. If your view is layer backed, that has a slightly different semantic than if you're in non-layer backed mode. In non-layer backed mode, the view is going to see its frame gradually changed incrementally from the current value to the new value.
If the view is layer backed, that's going to happen immediately in the application space because really the animation in core animation, most animations are just sort of a visual treatment that happen off in the render tree on a separate thread. And they're not really something that the app needs to be aware of. So that's something to be aware of. And one consequence of that is if you're moving stuff around, you're going to have a lot of things that are going to be in the way of the render tree.
So if you're moving stuff around really slowly, you have controls that you're moving around that the user may want to interact with, you might want to disable those controls during the course of a long animation because the user could be clicking around and, you know, hey, you know, it won't be clear at all to them that, you know, the app thinks that the control is in its destination when it's going to take another, you know, five seconds to get there. Most of the time, you're probably going to be using short animations of a second or less, maybe even a half second or so because you want your app to feel really responsive. Amen.
Also, layer content is not only, view content is not only cached individually into individual layers, but it's drawn axis-aligned in the view's own local coordinate system. So when you think about this, this comes into play when you have rotated views. Even though the view may be rotated, if it's in a layer-backed tree, that rotation is all implemented in LayerKit, and the drawing that the view does is actually aligned with the bitmap.
Even though the view is rotated in the window, all the drawing the view does is aligned with the pixels of the backing store. So that actually, in most cases, is sort of a benefit. That's part of why controls look so much better. If they're rotated, they look so much better in layer-backed mode, mostly, than in non-layer-backed mode, because everything just sort of lines up as it is. And effectively, we're just then taking an image and compositing that image in rotated. And so you just get some filtering and some anti-aliasing, but things mostly look nice.
But in some cases, you may have some negative effects from that. So something else to look out for. And that's another reason, in addition to performance issues, in addition to the fact that this has a performance cost, compatibility with existing rendering is another reason why we didn't just switch this on for all your views in Windows, because the output is slightly different. A layer-backed view's content may be composited in like an image. That's basically what we're doing. Views are not always going to necessarily be backed. They're not always going to be backed by a bitmap backing store.
There's also a facility for layers to have a display list backing store, which is a much more compact representation for vector-type art. And currently, as of the seed, we don't really have a way to take advantage of this yet in AppKit, but that's something that may be coming as a way to specify, "You know, this view just draws a gradient," or "It just draws a box, and it's really huge, and it's not worth caching it as a bitmap." But oftentimes, things are just composited in like an image, so they'll have those sorts of image compositing semantics.
In terms of getting the best performance from layer-backed views, the most important thing to do, kind of as before, is to avoid retraw as much as possible, only now, to avoid retraw, it pays off to isolate static parts of your user interface. So if you have stuff that is animating and moving and changing, and then you have some other stuff that is static and doesn't really need to be redrawn, it just needs to be recomposited into the scene, it pays to potentially separate that content into its own view. So you have the dynamic content. in one view and your static parts in some other views.
Likewise, you want to design by composing things together and applying effects because once you have content that's rendered, it's real easy and cheap, as we've seen, to sort of move it around and apply effects to it. But re-rendering content requires the app's intervention and can take time. So you can obviously have some sorts of animations of that type going on, but most of the time you want to try to avoid redrawing stuff when you don't have to. Real common sense.
Also, it's good to optimize your use of backing stores. You don't want to have a view that has a big margin around it and doesn't really draw anything out to its full extents, but just draws something inside of itself and leaves a bunch of blank space around it. You're just using a layer backing store. So try to crop your view content tightly and you'll get better performance that way and you'll be able to put more objects in your scene that way. Some possible future directions for this stuff for Leopard and beyond that we're thinking about.
We want to support additional animatable value types. So for example, colors. You should be able to interpolate colors. We're going to be adding stuff like that. AppKit does not currently interpret LK timing functions and keyframe animations for your own custom property animations, although for layer backed properties, that's automatically taken care of by core animation. So that we expect to be coming. There will be some further performance work on non-layer backed mode. So all of this stuff, and maybe it hasn't been clear in the session so far, all this animation stuff.
Also will work even if you're not using layer backed views. You can animate views frames by talking to an animator proxy. Part of the idea here was we wanted you to be able to use the same convenient syntax whether you have a layer backed view tree or not.
So we're probably going to do a little more improving of the non-layer backed mode, but right now the future really is layer backed views for really high performance animations, we think. So we've been focusing on that and that's the kind of stuff that you should be testing and experimenting with in the seed. and see, and flip layer back view mode on and start doing your animations that way.
There's some new API that was mentioned in the Cocoa What's New talk for taking a view full screen. You've got a view and some content and you want the user to be able to interact with it, filling up the screen. We want to be able to have transition effects applied to that. There's a provision for transition effects in there that's not yet implemented in the seed, but we'd like to be able to add that so you can get a nice smooth transition when you're going into full screen mode.
Troy Stephens Integration with other graphics technologies, as I mentioned, such as OpenGL, Quartz Composer, and QuickTime. There's a lot of potential here in Core Animation. This is one of the most powerful graphics technologies that we've had come along on Mac OS X. Troy Stephens And in fact, I've got a second demo, if we could go back to Demo D, to give you an idea of what kind of stuff is going to be possible here.
So I've got my slides demo. And it's got this nice gradient background and everything. And yeah, an NSGradient is nice, and that's fun to be able to draw. But you know what? If I wanted to be able to have something else arbitrary, like a Quartz Composer composition behind it or something. So I turn that on. And when I restart the app, it's going to work. Hmm. There we go, we got a little bit of a stall. Let me quit and restart and this will be happier.
The leopard's a little unhappy today. OK. This is a work in progress, by the way. OK, starting this up new. When in doubt, get out of the car, close the doors, reopen the doors, get back in the car, restart the engine. And let's see. OK, here we go. OK, so we've got a quartz composition here.
You know, we've got some arbitrary views on top of it. These happen to be sort of custom views, drawing these slide shapes. But they really could be anything. We could put sliders here. We've got the ability to have all kinds of different app kit controls there. In fact, I've got another little test app here we can run.
And this is a much more useless app except for just testing the animation stuff. So here I've got a quartz composition in the background. And I've got just a bunch of different app kit controls. And these are all layer-backed views here again. That's how we can, I mean we wouldn't be able to do this without layer-backed views.
There's no way to really have conventional view drawing on top of a quartz composition or any other type of OpenGL rendering, anything else that goes to a hardware surface because the surface normally floats over the window. So there's, this was something that was previously very hard to do. Now you're in the future going to have to do it again.
Troy Stephens And you're going to be able to have, you know, you want to put a slider on top of your movie view. We're looking to make that kind of thing really easy to do so you don't have to go to these sort of heroic lengths to do that kind of stuff.
So all these controls sort of work, you know. I can slide the slider. I can click the buttons. Let's see, I've got a color well, you know, I can pick around in there. It's live. It's interactive. You may notice as I'm mousing over the views, I've got sort of a halo filter there.
You can sort of see it there. I've got sort of a bloom filter that I'm applying there. Troy Stephens And again, the controls are all sort of interactive. They just work. Text fields aren't quite working yet well in the seed. We've got a fixed forthcoming for that. But most of the other controls, you know, they just sort of magically work. And if you want to animate them using layer backed animations, you know, that works too. Troy Stephens I can turn off use of layer kit here. And now we're just going to go to a static image because now we can't do the quartz composition anymore.
And I'm animating using the exact same APIs. The views have no idea that they're animating differently. And you can see, you know, it works. I can talk to the animator and set a frame target. And all the retargeting stuff works, too. But it's just not quite as smooth.
It's a little chunkier, right? So if we go back to LayerKit backed mode, you know, things just kind of flow. They're much smoother, mainly because the app isn't involved. You know, once your view content is rendered into layers, then Core Animation is moving stuff around behind the scenes on a separate layer.
And your app is free to do other stuff on its main thread, on its run loop. So, you know, this is the kind of stuff that we see coming in the future. And we hope to make it much easier to do this. Now, the adding of the quartz composition here in the background, that was actually done using API that's on your leopard seed. And what I did is I explicitly got a LK quartz, sorry, a QC composition layer. So there's a LK layer subclass that is exposed as part of the public API in the quartz composer framework that you can instantiate.
And I've substituted that layer. I've used the set layer API that's on views to substitute my own layer in place of the layer that AppKit would otherwise automatically create. And I just say, okay, I want you to create a quartz composition layer with this composition and blam, it's in there.
Going down the road in the future, we kind of want to make this even easier. You know, it should be possible to just have a QC view that's in a view hierarchy for which you happen to enable layer backed rendering. And AppKit should automatically know what to do, you know, what kind of special layer to create and how to configure it.
But right now, you can even sort of kludge this in right now just with what's on your seed. So some neat stuff is going to be possible now. If we could go back to the slides just for a wrap up. So where can you learn more about this stuff? There's some draft documentation that's available. Or it's called LayerKit still. Okay, the author has just reminded me that this is called, so look for LayerKit overview and LayerKit reference available, I think, via the ADC site and for download from the WWDC site.
And also, please read the Leopard AppKit release notes for this talk specifically and also in general. There are a bunch of notes there about the new animation functionality and should serve as a pretty good quick start guide for you to start experimenting with this. The source code for this sample we intend to make available soon. It's been really hard for me to stop playing around with it and adding new stuff.
This adding the quartz composition, I did that on the train this morning. I was just like, well, maybe we could just get that working too, you know, and it was easy to do. I added five lines of code and it just worked. So I'm going to try to stop futzing with it. We're going to try to get it published very shortly so you can look at how this is all done.
And finally, since we are using the LK Animations in the AppKit API, it might be useful for you to take a look at the Quartz Core framework headers, just a few of them, specifically LKAnimation.h, LKTiming.h, and if you want LKLayer.h, there are a lot of comments in the headers that will help give you more detail about how these things work and when to use which kind and so forth and how to configure them. So hopefully this has been helpful.
For more information, you can contact Derek Horn, who's our Application Frameworks Evangelist. And lastly, just some take-home points, if you remember nothing else from this session. The key idea I want to get across here is that to take advantage of this powerful new core animation functionality, there's no need to re-architect your user interfaces, maybe a little refactoring of where you put your static and dynamic content.
But in general, there's no need to rebuild your UIs. You can work with layers directly if you want to, but there's also no need to be aware that they even exist or that AppKit is auto-managing them. So what I'm showing you, except for swapping in that Quartz Composition layer, has no idea that there's any of this layer stuff going on.
It just does set once layer yes for the root view, and AppKit takes care of the rest. So it's all very automatic. We've tried to make it very automatic, while also providing you the opportunity to override behavior where you want to. So keep designing great UI using views, and let AppKit help you animate them efficiently.