Configure player

Close

WWDC Index does not host video files

If you have access to video files, you can configure a URL pattern to be used in a video player.

URL pattern

preview

Use any of these variables in your URL pattern, the pattern is stored in your browsers' local storage.

$id
ID of session: wwdc2005-146
$eventId
ID of event: wwdc2005
$eventContentId
ID of session without event part: 146
$eventShortId
Shortened ID of event: wwdc05
$year
Year of session: 2005
$extension
Extension of original filename: mov
$filenameAlmostEvery
Filename from "(Almost) Every..." gist: ...

WWDC05 • Session 146

Cocoa Advanced View Techniques

Application Technologies • 1:11:27

Learn through coding examples designed to show you how to implement advanced Cocoa graphics techniques. The emphasis will be on the new capabilities in Tiger, such as drawing redirection, NSAnimation, live resizing, view binding, and more.

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.

Hello. Welcome to session 146, Cocoa Advanced View Techniques. This will be our final Cocoa session of the week, so thank you very much for coming. My name is Troy Stephens. I'm an application frameworks engineer at Apple, where I work specifically on AppKit, and I'm here today to talk to you about Cocoa Views. And in particular, what I want to do is show you some of the neat kinds of things that you can do with Views in your applications by leveraging some of the fantastic new technologies and APIs that we have on Tiger.

But in addition to focusing on Tiger, I'm going to point out along the course of the talk a couple of things that are long-standing facilities that we have in Cocoa in AppKit and in Interface Builder that are maybe underutilized by developers. So first we'll dive in and just do a quick review of Views, what Views are, and then we'll dive into our content. So Views, as you know, are the basic building blocks of window content in a Cocoa application. They are your application's primary interface to its users.

They are the application's means of presenting its underlying data modeling. So you can see that the view is a view that is visually, in some form, visually, and giving the users a chance to interact with and edit that data model or whatever data you have to provide. You can think of Views productively as rectangles of responsibility. A view is responsible for drawing in its own content area. So when the window is asked to draw, the view has to draw its own content. Views are responsible for handling user input, namely, specifically, keyboard and mouse events. Views can be nested in a tree or hierarchy.

They support arbitrary, nearly arbitrary, affine coordinate transforms. You can do translations, scaling, rotations. So if you have some particular kind of data, like a graph you want to draw, and you have a particular convenient coordinate system you want to draw in, you can set up that coordinate system so you can work in it natively and handle user events in it natively. Views, remember, View is a kind of responder. So Views participate in the responder chain, which is a very important, flexible, dynamic mechanism that AppKit provides for passing messages around and handling action messages specifically, especially from, say, menu items.

Views are an integral part of the drag and drop architecture. You're always dragging from a view or to a view, usually, in a Cocoa application. And they are part of the unified drawing and printing architecture whereby, you know, if you've set up your view to draw itself on the screen, then you pretty much have everything or almost everything you need to go ahead and print that same content out to a printing output device.

And there's a whole lot more functionality in NSView. You have support for tool tips, cursor management, tracking racks, and so forth. So it's a very complex, integral part of the AppKit. And remember that controls are Views, too. A control is just a special subclass of View, a kind of View that typically delegates a lot of its drawing and event handling responsibility to an associated cell or cell instances.

And incidentally, controls can also have an integer tag value that Views don't have, which gives you sort of an old-fashioned but sometimes useful way to distinguish between different Views in a window and find Views. So, NSView is a very complex, integral part of the AppKit that gives us a lot of topics we could potentially talk about today. To focus our talk down, what I'm going to do is we're going to take a look at a concrete example application.

And we're going to look at the ways that some of these sort of View-related challenges that come up in the course of implementing that application can be addressed. And so, I was thinking about what might make kind of a fun or interesting and maybe even useful example app for today's talk. And I was thinking about one thing that I have a ton of nowadays, and we all have a lot of, is digital image files, right? I've got a digital camera that just keeps cranking them out, and I don't know what to do with them all.

And I get these images, and it's not so much the organizational problem that I want to address today, but typically, you know, the thing I most often want to do is I want to scale them down, right? I've got these, you know, say, 2 megapixel images. I've got kind of an old camera here. It's showing its age.

But I've got these high-resolution images that I want to take, and maybe I want to reference them. I want to reference them from a web page or attach them to an email, and I want to scale them down. So I thought, well, AppKit and Quartz contain ample functionality for doing this kind of thing and implementing a real lightweight tool for this kind of thing.

We have the ability to read and write standard image file formats like JPEG, TIFF, PNG, and so on, and also the ability to do compositing with different kinds of transforms. So we could actually do the scaling using AppKit and Quartz alone, but now on Tiger, we have Core Image, which is this fantastic library of images. It's a fantastic library of incredibly powerful, incredibly fast image manipulation capabilities. And so with all these capabilities combined, I launched off to make an application and produce this. This is Reducer.

This will be the example application that we'll look at today. And the source code for this is online. It somehow didn't manage to make it into the composite disk image for the conference. But if you go to developer.apple.com/sourcecode and search for Reducer, you'll find it's been online all week. And I've got the full source code for this, so everything I'm going to talk about today you can study in detail later.

So what are the basic features of this app? First of all, first thing we wanted to do, of course, is to downsample images, both to produce a smaller physical image, smaller number of pixels, and also to produce a smaller file for attaching to an email or pulling over the web. So that's the first thing we want to do. Now oftentimes, when you downsample an image, that kind of tends to blur it a little bit. It gets a little fuzzed out. So it would be kind of nice to sharpen that image.

Once upon a time, this would have been like, what am I going to do to find a library that does this? And now all this stuff is built in in Core Image. And Tiger, hopefully, had a chance to attend the Core Image talks earlier this week, or will have a chance later to review the recordings from those.

So now you can just take an image and hand it to Core Image and say, hey, you, sharpen this. And you get it back. You're a sharpened image. And there are all kinds of other things that you can do. So we're just going to keep it simple right now. But once you're hooked into Core Image, there are all kinds of things you can do with images. So maybe next, we want to add some decorations, maybe add a border to our images. Add a drop shadow.

And then we want to save the result out in one of these standard image file formats, like JPEG. And then maybe we're going to put it on a web page. So we might as well have this tool generate an HTML image tag, real simple, a string that we can copy to the pasteboard and paste into our web page.

So in the course of looking at this reducer example, we're going to cover a variety of techniques, including some new APIs on Tiger. We'll look at using NSAnimation, the new NSAnimation and NSViewAnimation classes that we've added to AppKit and Tiger to help you sort of liven up and animate your state changes in your views and bring your UI to life. We'll also look at using CoreImage in general together with the Cocoa applications. Very easy to do this. Very straightforward.

And we'll look at some new drawing, what we call the drawing redirection methods in NSView. It's a set of three new methods in NSView and a method in NSGraphicsContext that enable you to take a subtree of views, essentially, and ask it to draw somewhere other than in the window where it resides.

Or you can have a view subtree that doesn't even reside in the window currently, and you can have it render out to a bitmap or something, just that subtree, so that you can then use that bitmap for something. So in the course of looking at all these new things, we're also going to look at some stuff that's maybe not quite so new, but still very shiny.

We'll look at using an NSOpenGL context with an arbitrary NSView. And we'll also look at what's involved in creating custom views in general and creating palettes for those views so that you can really make them first class citizens in the Cocoa world. And then lastly, we'll also look at making the views when you do that.

How do you make a custom views attributes bindable in Interface Builder? So let's go over to demo one and first take a quick look at our completed example application just to get an idea what it does. So here it is. This is the reducer application version 1.0. And we've got our main parameters window here on the left, the vertical window. And it's got sort of an image well where we can drag in an image. Let's see, we've got some example images here.

And I can drag something in. And this is the result window here where we display a preview of what's going to be output to the file when we go ahead and save. So we've got our reduced image. I've got a 1600 by 1200 image. I've reduced it down. You know, we can drag in a different image. We can work with this one here.

And I'll maybe make it a little bigger. I would actually want to reduce it smaller, but so you can see a little more what's going on. So I've reduced it to about a quarter of the number of pixels, half the size in each dimension. And, you know, it's a little fuzzy, so I might like to sharpen it and clean it up a bit. And that adds some nice sharpness, really brings that face out. We can slide. We have an intensity parameter for the -- this is just a CI, unsharp mask filter.

And we can slide that along, get less sharpness, more sharpness, really somewhere in the middle. Moderation is a good thing, so right in the middle there. And there's a radius parameter that we can adjust to. We might want to add a border to the image, you know, give it some thickness. This is stuff that I'm doing after the fact with regular quartz drawings, Bezier path. And we can even round the corners a little bit. And change the color, you know, that Bezier path, real simple, straightforward stuff.

I like white. Let's stick with white there. Neutral frame. And we can add a drop shadow underneath it. So let's say then we really want sort of more of a 400 pixel wide image. Or at least the image itself when scaled down will be that. We add a little more for the shadow.

And then we're going to go ahead and click the saving tab here. And we have a variety of formats we can choose to save in. Maybe we'll save in PNG because here we have some transparency information. We have a shadow that we want to be able to drop onto any color background. So let's just save it as a PNG. And we'll give it a name.

Save it out. And there's our PNG file in the desktop. And I can open that up in preview and just look at it. And we can see that if I change the background color here, It's a little hard to see the shadow, and probably especially hard on that projection screen. No, you can't see it at all, sorry.

But it's there, and it obviously doesn't have fixed white behind it or whatever, so minor detail here. So let's say I've got this image, you know, and maybe I've got my photo album from my trip. I'll open this up in Safari. I've got some various pictures in here. So let's go ahead and open that HTML file in TextEdit.

It's just a real simple HTML file. Let's say I want to put that image at the top. I go over to Reducer, and this is real simple stuff, copying a string, composing a string. Since we happen to know the width and height of the image in pixels, we can go ahead and stick that in and not have to type it by hand. And we paste it in and save, and then go back and reload our photo album. And there's our picture there. And so back here. And we just keep doing this.

And so there you go. Oh, we got a couple copies of that in there now. So anyhow, so that's the reducer application. That's basically what it does. Something interesting to note about the UI here, you've noticed we have these collapsible boxes here that contain the different parameters in them. And so if, you know, I don't want to border, let's say, on the image, well then I have a bunch of parameters that are irrelevant.

So there are different things you can do in that. In that kind of case in your UI, you can just disable the controls and leave them visible but maybe you want to reclaim some real estate here, you know, so you can have that box collapse down and the one underneath it slides up and so forth. And this is all stuff that's done with NSAnimation and we'll see in a little bit how that is accomplished. So I think that's it for this demo and if we could go back to the slides please.

So that's the reducer application. I'm going to talk a little bit later about the image processing pipeline. It's mostly pretty straightforward. There's an image reducer object that's responsible for doing all sort of the heavy image lifting in the application. I won't go into the code in detail, but it's there for you to study. It's quite straightforward. And I'll just talk about the overall architecture later. But this is, after all, a ViewTalk. So I'm going to focus primarily on some of the custom views that I've used in this application, the collapsible box that I just mentioned.

And we've also got what you might think would be an ordinary image view here in the result window. But it turns out that for performance reasons, I've decided to implement what I call a hybrid image view. It's not actually an image view subclass. It's an ordinary NS view.

And what it can do is switch back and forth on the fly invisibly between doing quartz drawing and doing open GL drawing. And we'll look at how that can be done a little later. But we'll start simple with collapsible box and work our way to more advanced stuff through the course of the talk.

So how do we make this collapsible box? Well, it's pretty simple and straightforward, but it gives us a good context for this. First, why would you want to create a custom view at all? There are two basic reasons why you might want to do this, to my mind.

First, you might have an existing control whose appearance you might want to customize. An existing AppKit control, you want to change the appearance a little bit. Or maybe, as in this case, you want to change the behavior. You want to make something that knows how to expand and collapse itself.

So that's one case. Or you might have some general user-defined data that you need to present to the user. And it could be anything. You want to start with a blank slate. So you want to just subclass NSView and go ahead and do all your own drawing and all your own event handling. So you could do that, too. We're going to look at examples of both today, but we're going to start with collapsible box, which is an example of the former.

And this is real simple, so I'll go through it pretty quickly so we can get to the advanced stuff. We start by subclassing NSBox, because I decided I like the way it looks. It's got this nice inset when it's in a metal window, and It's got the rounded corners and everything.

The appearance is fine. I just want to add some behavior. So we're going to add a Boolean attribute to it that NSBox didn't have that just keeps track of whether the box is expanded. We'll also internally keep track of the other frame value, the other rectangle that the view should have when it's in its other state.

So we're going to add the expanded Boolean attribute, and then we're going to implement key-value coding compliant accessors for the expanded attribute. You know, naturally you want to implement accessors for any attribute. It's good to make them key-value coding compliant, and in fact it's necessary if you want to make that attribute bindable in the future.

So key-value coding compliance mainly, if you're not familiar with it, just has to do with the naming conventions for the methods, so that we can programmatically say, you know, we've got a string expanded, and we want to know what the possible getter and setter methods are. So isExpanded qualifies in this case. It's a Boolean attribute.

So we're going to have an isExpandedGetter method that reports the current state of the view, and then we'll add a setExpanded method that sets the view, toggles it to the other state, assuming that the state you're asking for is different from the current one. And when that gets invoked, it's going to remember the current frame size of the view, and then toggle to the other frame size that it's cached.

And to make things more interesting, we're going to animate that transition to the new frame size, and in effect, we'll also look at animating groups of these boxes together and what it takes to coordinate that. To facilitate this kind of animation, I mean, you could kind of do this kind of stuff yourself before, but now we've got a built-in facility in AppKit that's the beginning of probably a growing facility that we'll have for supporting all kinds of animation stuff. If you look in NSAnimation.h, we've got two new animation classes.

NSAnimation is the base class, and it handles all the basic functionality that you might expect. So you can do some timing, you know, keeping track of the current time value and advancing it as the clock ticks, the real-time clock ticks by, starting and stopping animations. You can do more sophisticated things with chaining multiple animations together. So say you have animation B and you want it to start, you know, three-quarters of the way through animation A or when animation A finishes, so you can do that with NSAnimation, the functionalities in the API.

We also have some built-in animation progress curves to give you sort of -- so you don't have to have, like, just a linear motion. You can have some acceleration and deceleration. We define a few standard curves for you. And there's a delegate mechanism in there so that you can do certain kinds of customization of NSAnimation's behavior without having to subclass it necessarily.

But for most everyday stuff that you want to do, you might want to just, you know, move a view across your window or resize it a bit or fade some things in or out. And for that, we provide NSViewAnimation, which you can use just out of the box to do some real basic kinds of animations, moving views, changing their origins, changing their frame sizes. We stretch and shrink the content as we do that. And you can even do some fading in and out kind of effects with that.

Here again, we have four standard animation curves built into NSAnimation. NSAnimation Ease In/Out might be just the one you want to use by default. It is the curve that we use. For sheets and drawers in the Aqua UI to give them sort of this accelerate slowly and start moving and then decelerate slowly at the end. So if you want to have consistency, you can use that curve.

We've also got the ease in curve and ease out curve. And if you just want a linear function of position as a function of time, you can have that. And if you want to add additional user-defined curves of your own imagining, you can certainly do that by subclassing NSAnimation, for example.

So let's say you want to take this and use it to animate one or more views. What do you do? I'll go through this fairly quickly because this was also discussed in the Cocoa Makeover talk, so I assume maybe you've had a chance to see it. In any case, it's fairly straightforward.

First, we build an animation description, which is an array of NSDictionary instances. And then we're going to pass that animation description in when we create an NS view animation instance from it. So basically, a single NS view animation can coordinate many simultaneous animations. And so you just create a dictionary for each thing that you want to animate describing how you want to animate it.

Once we have the NSView animation instance, we set the duration of the animation. We want it to be one second, half a second, 10 seconds. We can set that. We can set the blocking mode. Animations can run both synchronously or asynchronously. So if it's synchronous, it's blocking, in other words.

Then when you tell the animation to start, you're not going to return from that start animation message. It will return to you after the animation has finished. So if you're doing your animation blocking, then as soon as you do start animation, you can come back and go ahead and release the animation instance. You're all done, and you can clean up.

So what this looks like in code is this. This is the first step where you set up the array of dictionaries. And each dictionary is fairly simple. We have a target key that specifies either a view or a window that is to be animated. That is required. And then you have optional start and end frames.

Usually the start frame, you just let it be implicitly set as the current frame of the view. You're going to start where you are, define a new end frame, and then you optionally can specify an effect, which right now is either fading the view in or fading the view out.

Most of the time, if you're just going to move a view or resize it, you'll have a target and end frame, and that's all you need in there. And then you have additional dictionary instances for each thing that you want to animate. Now we create our NSView animation, just like I said, pretty straightforward.

Pass it the array of dictionaries, set the blocking mode that we want, duration, start it, and then we're doing it synchronous so we can release it. So let's look at this back on demo one. And I will open up--we've got two projects, project files here. I'm going to go to the top level project, which is Reducer, the application itself.

And here's our collapsible box class. And as you can see, it's mostly boilerplate legal disclaimer. It's a simple subclass of NSBox. Here's our expanded attribute here. The public attribute that we have accessors to set. Here's where we cache the other frame size. We also have a facility for a delegate that can be used to help customize the animation behavior. I'll talk about that in a minute. That's used to coordinate animation of multiple sibling collapsible boxes. We've got another form of the SetExpanded accessor that is actually the final point for SetExpanded with an animate parameter so we can tell it to expand or collapse without animating.

For example, we'll want this later in Interface Builder. We'll look at that. So if we do have a delegate, it's going to implement this method that we'll go ahead and send. We'll say, "Okay, if we have a delegate, should this collapsible box animate to this new frame?" And you can tell me yes or no, if I should do it or not, and we pass in whether we're expanding or collapsing. So let's look at the implementation real quick. It's pretty straightforward, as you would expect. SetExpanded by default calls SetPanded. By default, it calls SetExpandedAnimate with a parameter of yes.

So we go in here. This is sort of our main funnel point. If we're going into a different state than the current state, remember the new state and get the other frame that we've cached and animate to the new frame size using this method here, SetFrameSizeAnimate, which when we go to that-- is going to check, you know, first, do we want to animate this or do we want to do it right away? Let's assume we're going to animate it. We calculate the new frame, and this is only non-trivial because remember that the default coordinate system for most views is not flipped.

So the y-axis is pointing up. So in this case, we have these boxes, and when a box resizes smaller, we want the top edge of the box to stay pinned and the bottom edge to kind of slide up there. And what actually, what this means when you're in a non-flipped coordinate system is that the frame, you're actually changing the origin too, right, because the origin is down on the bottom of the box.

So we've got to both shrink the size of the box and also shift it up by the same amount at the same time. So all this is is a little utility method here that will figure out whether the parent view is flipped and do the math and get the new frame for the new size so that we stay pinned to the top. Now here's our chance for animation delegate, if we have one, to handle the animation itself. And if it reports that it doesn't want us to do the animation, we go ahead and return out.

We'll look at that in a bit. But ordinarily, let's say we just have this box that's standing alone and we just want to expand or collapse it, then this is the simplest case of the animation. We're creating the animation array. This is all exactly like I had it on the slides. Here's the single dictionary we're passing in because we're only animating this one box, so the target is self.

And the end frame is the new frame that we calculated wrapped up in an NS value so we can stick it in a dictionary. And we go ahead and set the parameter, create the animation, set the parameters, start it, and release. And then if we're not animating, well, we'll go ahead and just do a very quick, immediate set frame size.

So we have the option of not animating. Now what are we going to do, though, when we have-- if we look at Reducer, we have these boxes. And normally, I expand and collapse this. It doesn't just expand and collapse in place. We have these other views beneath it, the border and shadow views that slide up and down.

How do we coordinate all of this? These are all sibling views here, so we need some sort of higher level object that's ideally outside of the collapsible box itself that will coordinate it and its siblings in animating. So that's what this delegate mechanism is for. So we check here where we checked at the top, as I pointed out earlier, if we have a delegate and the delegate responds to this selector that we've defined in our sort of informal protocol, we'll ask the delegate, you know, should we go ahead and animate this? And if it says no, then we go ahead and return and we assume that it's going to handle the animation itself. So in the case of the reducer app, the main controller is the delegate for this animation. It's just sort of the central controller object of the application.

And here's our delegate method that's going to get called by whichever box happens to be getting expanded or collapsed. This message is going to get sent to the main controller. And it's just going to go ahead and send a message to its own class. I've just abstracted this out into a class method. That's the way the code's structured. And we'll return no and say, you know, we've taken care of this because I want to coordinate these animations and do them myself.

So here we've got this method that I've defined, animate view and siblings target frame. I just arbitrarily designed that was going to be my method signature there. And so what we need to do is first compare the new target frame to the current frame of the box that's being collapsed or expanded. And from that I get this y offset. I calculate the value that tells me how much I need to shift the boxes below it up or down.

And we go ahead in here. We're going to, again, create a mutable array that will be our animation description list. And then we know we want to animate the target view that's been passed into us. That's the box that the user has expanded or collapsed. So great. We're going to add that one. Now we're going to go ahead and get a list that includes all it and all of its sibling views. So we'll get the target view super view. We'll ask it for all of its sub views. And then we'll iterate through them, real simple iteration by index.

And for each of those siblings, as long as it's not the target view, which we already took care of, we'll get the old siblings frame. And basically, we do a comparison here, a coordinate comparison, to see, hey, is that below the view that I'm expanding or collapsing? In other words, do I need to animate it? And if so, we go ahead and apply the same y offset there to its frame.

Add that. So we're building dynamically, in this case, the animation list. And then once we have that description list, we go ahead and instantiate the NSVU animation, all straightforward stuff. And we're going to do it blocking. And we're going to shoot for about a quarter of a second. And it will just go. And then we release our animation resource. And we're done.

You know, one further step that you might take this is, you know, okay, we're reclaiming this window space, you know, maybe we want to actually, you know, shrink the window or something, unless we're going to use it for something else. Maybe if we had our image view down there, we might want to have the image view expand or something like that.

But in this case, we might want to animate the window too. So how might you do that? I'll leave that as an exercise to you. I'll probably add it at some point in the reducer example in a future version. But if we can go back to slides, I'll tell you briefly how you would do that.

Naively, you might think, and I would do this too, you might think that you could just build an animation description that includes not just these boxes, but say the tab view that surrounds them, and also the window itself, and just go ahead and execute that animation. Well, due to the way that we kind of have to cache things, we try to make things efficient, we tend to cache images of views, that might not just work.

In fact, it won't just work right out of the box that way. But the good news is, is that we have this existing facility for view auto-sizing, the springs and struts model that you're familiar with, setting an interface builder, setting your springs and your views so that they, the layout of your window updates.

If your window is resizable, the user can drag it any which way, and your window will automatically resize itself. So if you leverage this facility in conjunction with NSViewAnimation, then all you really need to do is use a single view animation with a single target, animate the window, and let the springs and struts do the rest. And this is what we're going to do. This gives you, this is a way that you can combine an existing Cocoa facility with a new NSViewAnimation stuff to do something more complicated.

[Transcript missing]

And this will create a project with two targets, one that is a framework that contains just your views, and one that is a palette. And what a palette is is a loadable bundle. It's sort of a plug-in that Interface Builder knows how to load that follows certain semantics for being an IB plug-in.

[Transcript missing]

So there are a few further steps required to put a view in a palette, and I'll be daunted by this list. Only the stuff on the top is really required, and it's really pretty straightforward. But the main gotcha to watch out for is when you instantiate most palettes in Xcode, most templates, sorry, when you instantiate a typical Xcode template like new Cocoa application, new document-based application, you can usually just go ahead and build that, and right away you get a functioning application.

But because of the way the limitations in the template instantiation mechanism that's currently there, there are going to be a couple things that aren't set that are going to break this. So what you need to do is go into your two nib files, your palette nib and your inspector nib that are created with the template, open those up in Interface Builder, go to the files owner object, and you'll find that its class is not set.

If you go to the fifth panel in the inspector and to the custom class, you'll see that it's not set. So all you need to do is set it to the name of your existing inspector class, and then you can build, and things will start to work. After that, what you need to do for each of your custom views is just have them implement the basic archiving methods, init with coder and encode with coder, so that your view knows how, not only how to archive itself out to a nib when Interface Builder saves the nib, but also remember archiving is the mechanism used for drag and drop and copying stuff on the pasteboard.

So, you know, when you go in and you've got your custom view on your palette and you start dragging it out into a window, we're actually going to archive that instance. It's a live object. It's going to get archived, put on the pasteboard, and when you drop it, it's going to get unarchived, and you've got a live object in your window.

So this is a requirement, but it's something you might be doing anyway. And then you want to implement all your--make sure that all your setter accessors for the view for various state that affects how the view looks. We'll do a set needs display. We'll tell AppKit, "Oh, my state has changed. This part of myself or the whole view needs to be redisplayed at some point.

You're probably going to need to do this anyway, but I just point it out because it's important for interaction in IB." And then you're going to want to factor out some of your code for initializing the attributes that your custom view class does not inherit, all the ones that it adds to its superclass.

And we'll look at why that's helpful in a little bit. Then you can add some optional polish from there. You can build an inspector for your view's properties. That, I would say, is some very good polish to add that completes it. You can add, you know, a little icon for your palette that will show up in this sort of toolbar-like strip that's at the top of the palette. You can expose interesting attributes of your views as bindable. We'll look at that. And you can do other--there are some other neat tricks you can do.

Like, for example, if you want to display sort of a preview stub of your view down in the palette that looks different than your view or is maybe shrunk down because your view is way too big, you can just put something like an image view in there with a stock image. And we provide a place where you can do some sleight of hand. And as it's being dragged out, you can swap in your own actual object instance, your own image instance, your instance of your custom view in place of the image, let's say.

So there's some tricks like that you can do. But the basics are pretty simple, right? Just instantiate the template, the IB palette template. Set the file's owners in the two nib files. Make sure you implement archiving. And you're pretty much ready to go there, at least for the basics. So I mentioned exposing bindable attributes for your custom views. How exactly do you do that? First step, as I said, is to provide key-value coding-compliant accessors. We already looked at this.

You follow the naming convention that's specified in the KVC documentation, and you will be golden. And this is really all you need in order for this particular attribute to be bindable programmatically. But let's say, you know, we've got this bindings inspector in Interface Builder, and you want to be able to bind to that visually in IB.

So what you want to do is use the expose binding message that can be sent to your object's class. So a good convenient place. You do this once for each attribute you want to expose, and you do it once for the class for each attribute you want to expose. So a good convenient place to do this is in your class's initialize method. Here we have collapsible box.

It has an initialize method that we've given it as a class. And we go ahead and we want to expose the expanded binding. All you do is specify the string value of the attribute. The name of the attribute is expanded. And with key-value coding, bindings will know that it should use set expanded and is expanded to access that attribute.

So you go ahead and do this once for each of the attributes you want to expose, and that's pretty much it. And then you end up with, here on the right-hand side, you can see our attribute is right there in Interface Builder. And you can go wild with this, keep adding attributes that you want to bind, and key-value binding does the rest pretty automatically.

Now you want to create an inspector for your view, let's say. This is pretty straightforward and simple. You just need to create outlets. You're going to presumably have some controls that you want to add. In this really trivial example of collapsible box, you've just got an expanded checkbox that you want to put there in the inspector in IB, and that's going to enable you to toggle the expanded state of the box while you're inspecting it. So you're going to create, we'll create an outlet from the inspector object, which is sort of a controller for that inspector panel, remember, and we'll, so we can connect to that box and talk to it.

Then we will wire the action, the action, target action message of that checkbox and of any other controls that we decide to add to the inspector so that they send the okay message to the inspector. You'll find in the template when you instantiate it, there are stubs for okay and revert message.

Okay is for committing changes from the inspector controls into your view instance, and then revert is for doing sort of an undo operation where we take all the state from the view itself and push it into the controls. So this is all sort of done, you know, pretty simply the old fashioned way right now using the sort of target action approach.

We realized it would be desirable to be able to build inspectors with bindings one day, and we're looking at ways that you could do that. We don't have a recommended approach just yet. but that may be coming so So last thing I mentioned, it's handy for reasons that we'll see in the demo shortly to take the initialization of default values for whatever attributes you add to your view and factor that out into a separate method, because you're going to want to be able to call that from two different places. The palette object has a finish instantiate method that you can think of as pretty much-- this is a wake from nib for a palette, more or less.

This is the message that IB sends the palette after it's loaded up, and now you get a chance to do whatever other initialization you need to do to get your palette ready to go and to get the object instances on it, in this case, fully initialized. So let's go back to demo one, please. And we'll take a quick look at the palette. It's fairly straightforward.

So let's see. Once you have the sample code, you'll see there's a collapsible box subfolder. And this here is my IB palette. I built this exactly like I told you, from an IB palette template in Xcode. And I've since added a couple of custom views to it. So we've got our collapsible box here, referencing the same source files that's built into the framework. Here's our palette target. And among its resources, it's got a single palette nib.

And if we open that up, we can take a quick look. It's got our collapsible box here and a couple of other custom views that I'll get to later. Then we've got an inspector for the collapsible box, which is really just boring and dead simple. There's our expanded check box.

And then here's our palette object. Pretty straightforward. We've given it an outlet to each of the prototype instances on the palette so that we can identify, okay, here's our collapsible box prototype that people can drag out. Also, and you'll find this is automatically done for you below the template, Interface Builder requires every view that you put on a palette and want to have an inspector for to implement this method inspector class name. That all it does is return as a string the name of the inspector class, which usually is pretty simply composed or derived from the name of the class. So here we have a collapsible box inspector that corresponds to our collapsible box.

And here in the palette, we have our finish instantiate method, and we're going to tell each of our prototype views to finish doing its initialization. And for collapsible box, what we have here, we just have this expanded attribute. We want to make it yes by default, let's say. And we want to assume some default other frame size for when we toggle.

So we're sending this init added properties message from init with frame so that we're invoking a common code from there that we would otherwise maybe put inline right there. And then we're also using it from the palette object, as I showed you here. The inspector is pretty straightforward.

I won't spend a lot of time on it. Most of this is boilerplate code, but the interesting stuff is the stuff I've inserted here. Here we're setting the expanded attribute of the object from the checkbox state and vice versa for the revert method. And so if we go into interface builder, you know--oops. Oh, that's not--oh, we seem to be missing our templates again. Okay. Well, let's say I have an existing nib file, which I do here, hopefully.

Okay, let's just take this here and kind of mess with it. So I can go here and here in my palette view, This here is our collapsible box palette, and it's just loaded right up there. So it's a peer along with all the other-- here we have our controllers for key value binding and so forth.

And it's a first class citizen right there. And I can take my collapsible box and drag it off. And I've got-- this is the box itself, and I've got a few controls in it. I've got a default disclosure button. Let's say that I want to bind the value of that button to something.

The focus of this presentation is on the new capabilities of Cocoa Graphics. The emphasis will be on the new capabilities in Tiger, such as drawing redirection, NSAnimation, live resizing, view binding, and more. Troy Stephens You can see it in the inspector. And this is our attribute that we exposed, and it's bindable now.

Whoa, I'm getting in a little trouble with that. Let's bind the expanded attribute to the same value. And so when we go into test interface mode, it doesn't quite sync up till after the first time. But now, you know, this works right in test interface mode. And yet, we also want the box to toggle immediately.

When we toggle the attribute here, we don't want to bother animating it. So we've got some special behavior in there. And that's just one more bit of code I want to call your attention to here. I should call my own attention to it first. There's a check you can make to see whether you are running in Interface Builder.

I'm sure I will find this code later when I am looking for something entirely different. So let's go ahead and go back to slides because I want to move along. We've got some more stuff to talk about today. So next thing I want to look at is implementing this hybrid image view that I talked about.

And we're just going to look at this really briefly. I'll give you an overview. I won't go through the code step by step. And to understand why we might want this, we need to understand a little bit about how the image reducer works. The image reducer, again, is the image reduction engine in the app.

And it's just sort of a black box, or I guess a red box here in this slide. We give it an image in. We give it some parameters. We get an image out, and we can save it to a file or display it on the screen. We're going to use Core Image to do some of this work. And when Core Image was added to the picture in Tiger, we added some bridge work to AppKit to help make it easy for you to convert between the two worlds, because we really do have two worlds with sort of similar but differently implemented concepts.

In AppKit, we have support for images, of course, and different image representations, including bitmaps. We have our concept of color and graphics context, and Core Image is the same. Core Image has a CI image, which is really sort of more a recipe for drawing an image on demand when you ask for it to be composited somewhere. And they also have a CI color representation and a CI context for drawing.

So we added some bridge work that you will find declared mostly in NSColor.h and NSCIImageRep.h. So we've created a new image representation class that you can add to an image. So you can now have a--if you have a CI image that you've taken out of a filter, you can add that by wrapping it in an NSCIImageRep. Just add it, and then you can add that to an image.

And anywhere you can use an NSImage in Cocoa, you can have an image that is defined by a chain of Core Image filters that does some processing when it actually composites the image. So that sort of makes it more invisible that you're using a CI image. And likewise, we also have the ability to convert a bitmap image rep to a CI image. Vice versa, we can convert between an NSColor and a CI color.

And if you have an NSGraphics context that you're drawing into, you can get a CI context from it so that you can then ask the CI to do the same. You can then ask the CI context, "Hey, draw this CI image effectively into this NSGraphics context." So using that, how do we implement the ImageReducer class? We'll zoom in a little bit. Again, we've got our black box here.

Put an image in. We've got a bitmap image rep out. And how that image processing pipeline works is... We start, remember an NS image in Cocoa can be any kind of image. It might be a PDF image, it might be something vector maybe the user has dragged in. It's not necessarily a bitmap, or if it is a bitmap, it might be what we call a cached image rep that is the result of some processing that we've kept in an off-screen window somewhere.

So it's a bitmap, but it's not in main memory, so you can't access the pixel data quite the same. So, but what you can do, you get an NS image, you can walk its list of representations and see if it has a bitmap image rep. If not, we can create one, a new one, and composite the image into it. So we've got our bitmap image rep, and now we want to convert that to a CI image, because you can only make a CI image from a bitmap.

And then we bring Core Image in, and we want to take that CI image, and we'll make it the input image of a scale transform filter. The Lanso Scale Transform that's in there is kind of a nice scaling filter that will reduce the amount of blurring you get, but maybe we still want to sharpen the image, so we'll take the output of that filter, hook it into an unsharp mask filter, make it the input image to that.

Now the next stuff we want to do is add the border and shadow, right? So we want to do those. You can maybe, with some tweaking, you can figure out how to do this kind of stuff right in Core Image itself, but let's say we need to do this in Quartz, so we're going to take that CI image that's the output image of the unsharp mask filter, go ahead and create a bitmap image rep from it. So we're basically, at this point, we're forcing the CI image to render and give us some pixels that we can access in main memory. Once we've got that, it's easy to go ahead and add the border and the shadow, and that's pretty much it.

And now that we've got this architecture, it would actually be trivially easy to add additional layers, additional effects, let's say after or before the unsharp, let's say, you know, we want to apply a bloom filter, some kind of artistic effect, you can go ahead and do that, and it's dead easy once you've got this. You can go ahead and insert new filters in the chain, and it's really building up the groundwork that takes the time.

It's trivially easy to extend, so you can do that as an exercise too. But there's sort of an interesting performance issue here, because as I said, when we go to grab the CI image out and get a bitmap from it, obviously we have to do that if we want to save it as a JSON, peg say, but let's say that we want to Let's say that we're just displaying it on the screen.

We don't necessarily have to do that. And when we force the CI image to render into a bitmap image rep, we're pulling pixel. We're not only rendering the pixel data, which is probably happening on the graphics card, but then we're pulling the pixel data across to main memory, which is sort of the fast path. That's not the reverse commute. That's not the way you want to go. So that's potentially costly to do.

So let's say maybe we're not adding a border and a shadow. We want to be able to take a shortcut and keep all the data on the graphics card, which is where Core Image usually processes stuff. Let's say we want to just get the CI image out in that special case. And what that enables us to do is then render the CI image more efficiently. And the most efficient way to render a CI image is directly using OpenGL.

So what we'd like to do is draw using Quartz sometimes or draw using OpenGL other times. So you may be familiar with the NSOpenGL classes. We have three in AppKit that provide basic bridge work so that you can use OpenGL in your Cocoa applications. We have NSOpenGL context, which is a context to which you issue rendering commands, pixel format that's just used to create the context. And then usually, customarily, you use the context with an NSOpenGL view. And that OpenGL view you can create in a palette or whatever.

But a thing to note about NSOpenGL context that people don't always notice is that when you look at the accessors for associating it with a view, they just specify an NS view. They don't specify an NSOpenGL view. And in fact, NSOpenGL view is kind of really a lightweight convenience. You can really just take any ordinary NS view and point an NSOpenGL context at it and do some GL rendering into it. And so that's what we're going to do here.

So I'm just going to talk briefly about how you do that. Once you know that you can do this, it's very straightforward how you do it. You create an instance. Let's say we want to switch to OpenGL drawing. We create an instance of NSOpenGL context. We connect it to the view by sending it a set view message. OK, here's your view that you're going to draw into. We'll make the context current by sending it a make current context message, and so therefore, all subs are subsequent OpenGL API calls will go to that context in our current thread.

We go ahead and perform whatever OpenGL initialization we might need to do, setting up coordinate systems and drawing parameters and so forth. And then we'll go ahead and draw the views content using GL calls. One thing that can happen here if we implement this naively is that we might get sort of a brief flash on the screen as the user switches into OpenGL mode or out of OpenGL mode.

And what's going on here is under the hood, when you associate an OpenGL context with a view, you're going to see that the interface is actually creating a hardware surface, which is sort of a little backing store that floats over the window and Quartz compositor composites it in. So it just kind of looks like it's just in the view, but it's really floating there.

And so that surface comes in, and the compositor in Quartz might go ahead and flush everything out and composite it to the display before you have the chance to render your first GL frame. So what we've added in Tiger is a minor new feature in NSWindow to help with this kind of situation. And it's called the "disable screen updates" and it's a really simple method.

It's a little bit more complicated than the other methods that people sometimes encounter. We have this method, disable screen updates until flush. You send this message to a window, and all it does is it immediately tells the window-- it go ahead and uses the public NS disable screen updates call to suspend screen updates, and then the window makes a note, OK, next time I'm flush to the screen, next time I do some drawing, I'll go ahead and re-enable updates afterwards so everything can flush out. So this is a feature that you can use to avoid that. You might find it interesting when you get your hands on the sample code to comment out these lines, and just so you can see what happens when you don't do this.

So now we're in open jail mode. Maybe we want to switch back to Quartz. We need to do Quartz drawing. Well, that's even simpler. We dismiss the context surface. We send the context a clear drawable message, and so the surface will at this point disappear. So we might also want to do a disable screen updates until flush before that.

And then we're done with the context. We just release it, and now we can go back to doing normal drawing. In this case, you might want to disable screen updates because the window backing store is now going to be exposed, and maybe it's out of date, and you haven't had a chance to draw it yet. So you want the surface disappearance to not show up until after you've had a chance to update the window. When you do this, it's pretty seamless. And we can see this in our demo. If we go back to demo one, please.

I've got Reducer running here. And this here, this is it. This is, OK, right now we're rendering using OpenGL. I know that because we're not adding a border or a shadow. If I add one of those attributes, OK, now we're rendering using Quartz. And if I'm putting you to sleep right now with this demo, I can completely understand, because there's nothing to see here.

From a user's perspective, it's the same thing, and that's kind of the whole point. You're just doing something different under the hood. But if you study the example code, you can see how this has happened. So to try to sort of wake you up for the next session, I'll try and show you something a little more interesting than that.

There's one more custom view that we haven't talked about yet here. I decided, why have an ordinary tab view, boring tab view here, when we could really spice it up? And we've got all this stuff in Core Image, and in fact, we have some transition filters. So why not have it so when the user clicks the tab, we do a little ripple animation, right? And we'll do this again in slow motion, because you have to see that and thank you And all the credit for this really goes to the Core Image guys.

I mean, they've done amazing work on this stuff. And this is just stuff you can take and use out of the box. And especially now with some of the new API facilities and AppKit that I'll talk about briefly, you've got a combination. You can do stuff that, I mean, we haven't even anticipated yet, I'm sure, and I can't wait to see some of it.

So this is the kind of thing-- A few things that I want you to notice about this. Let's see, can I magnify this? When we do the tab switch, notice that the tabs at the top and their labels are not animating. And we've got the border that's still drawing as normal. And in fact, the metal window background behind this is staying static as we animate. So we're really just-- we've got the content there, and we've got it in some sort of transparent representation where we can just blip the content on and put it through the transition filter.

And in fact, here I've got just a little IB test window. And if I go to Interface Test Mode here in Interface Builder-- or actually, if we look at these first with the inspector, We'll see that each of these, and I've got an inspector for this, I've put some radio buttons. We've got eight standard transitions that Core Image supports out of the box and the potential for people to add more. But I've got, so I've got one, you know, tab view here with some random stuff in it.

Using the copy machine transition, the disintegrate with mask transition, dissolve, flash, modulate, page curl, ripple, and swipe. And I've got these all bound together to the same value, so if I just start them going, they'll all show you it. You know, once I had this working with one of these transition filters, it was really nominally easy, it was trivial to get it working with each of the other ones. And again, thank you to the Core Image team for doing all this stuff and making me look good here.

So, what I really want to talk about in the time that we have left is how do we do this? Because, really, you need this. You need to have this in your applications, right? This kind of stuff. This is mission critical. So, if we could go back to slides.

So I kind of lied. We got one more custom view in the example that I didn't mention before. We've got the animating tab view, which is a subclass of NSTabView that does this fancy transition. So we'll first look conceptually at how we do this, and then I'll walk you through the code. It's pretty straightforward once you figure out how to do it. So we're combining three powerful capabilities here to do this, to pull this off.

We're combining, obviously, core image transition filters, which are doing most of the heavy lifting. And then we've also got an NSAnimation that is just driving the time value of the transition filter forward, because you set up a transition filter and you iterate your time from 0 to 1 to advance the animation until it's completed. And then, as I said, we kind of want to be able to grab these sort of transparent snapshots of view subtrees so we can know what we're animating from and to as bit mapped images.

So we're using new view drawing redirection methods that are available in AppKit in Tiger in order to facilitate this. So here we've got our-- this is our basic model. We get a snapshot of the content that we've currently got right before we make the transition, and then we grab a snapshot of the content that we want to transition to. So we've got two bitmaps. We use those, wire those up as the input images of a CI ripple transition filter in this case, or any of the other transition filters.

We use an NSAnimation to drive the time value again from 0 to 1 over the course of the animation. And then at various points, at intervals along the course of the animation, we're going to sample the output image. We ask the ripple transition filter for its output image. It says, OK, gives us a CI image. And then when we composite that in to the window, we'll get the state of the animation for that frame, for that instant in time.

[Transcript missing]

And you end up with this. You end up with something that includes transparency in the background if your view subtree doesn't completely draw opaque over everything. And so you can put it on top of any kind of background you want, and you can see that it only draws where those controls actually draw.

So this is the complete set of methods that I'm referring to as the drawing redirection methods that we've added in Tiger. The one for getting the bitmap, one for caching display in REC. There's sort of a lower level funnel point method that actually implements most of the machinery of getting that subtree to display into that bitmap. Or actually, in this case, it lets you display into an arbitrary context.

Usually this is a graphics context that you create with a bitmap. This complements the existing display methods. We have several methods, I think nine or ten or so, on NSView that start with the word display. And they all kind of do different flavors of the same thing. This is a new one, Display Rect Ignoring Opacity in Context.

All that ignoring opacity means is that for the view display methods that don't include ignoring opacity in the name, they'll actually say, oh, well, I'm not an opaque view. Maybe I need to walk up the view tree and ask some ancestors to start displaying because I need their background behind me so that I can draw, because I think I'm drawing in a window.

But for drawing-- you just want to ignore that opacity and just start with the view that the developer has asked you to start with, and go ahead and recurse down and draw the subviews. So all that CacheDisplay in Rec does really for you, it creates an NSGraphicsContext for drawing into the bitmap image rep.

This is a thing that you can use separately from ViewCaching 2, the new facility here. You see NSGraphicsContext has this new method for allocating a graphics context that you can use to draw into a memory bitmap. So the CacheDisplay in Rec method does that for you, creates the graphics context, and then it will also set up a translation.

So if the rect that you're sampling is sort of a subrect of the view, then you can--usually you want to just cache, you don't want to allocate a big huge image just to cache this part of the view just because its origin happens to be over here. So we set up a translation to translate that portion to the origin of the bitmap for you.

But if you want to do some other kinds of transformations, before you do that, you can do that. Before we do the drawing of the view subtree, you know, knock yourselves out, you can use the DisplayRecIgnoringOpacityInContext method as your funnel point to do that kind of stuff and just sort of set up the graphics context yourself. It's pretty straightforward to do. So let's go back one more time and look at the example code for this.

and we're running a little short on time, so I'm not going to spend too much time on this, but once you've got the concept of what we're doing here, the rest is just mechanics and it's pretty straightforward. So here's our animating tab view. And we've actually got a declaration in the header. These just map our constants that map to the different transition filters that Core Image currently provides us.

So the attributes of-- the main inspectable attribute, the only inspectable attribute of the animating tab view is its transition style. And then also we have some state that we maintain during the course of the animation. Here's the transition filter, a pointer to the transition filter we're going to use.

A shading image that some of the filters require an auxiliary image that's used to help composite the effect. An input mask is something that other filters use. Then a rectangle in which we're going to composite the image when we're done. An NSAnimation to drive the time value of the transition filter. And then a little flag in here that lets me do that spiffy slow motion demo.

I love it when it does that. OK. So interesting stuff here is it turns out that Select Tab View Item is pretty much the funnel point for anything that can possibly change the current selection in NSTabView. And so that's very convenient. That's what I'm going to override as our means of doing the animation. And oh, here's the thing that I wanted to show you. I told you it would turn up when I was least looking for it.

So for example, when we're inspecting one of these in IB, if I change the tab item, I don't want to sit through the animation there, especially if I set to a long duration or something. I just want to do the immediate switch when I'm editing the interface, have everything be really quick in IB.

So here's a way that you can test whether your class is running under Interface Builder. Interface Builder's application instance-- remember, there's a global NSApp variable. That's one of our few global variables in Cocoa. It points to your global application instance. If it responds to the IsTestingInterface selector, you pretty much can assume that this is Interface Builder that you are running under.

And then if you know that you're running under Interface Builder, there's a further query that you can do called IsTestingInterface. So you can say, OK, I'm in Interface Builder, but am I in Interface Test Mode, or am I just in normal editing mode? So here I'm saying, OK, if I'm just in normal editing mode, not Interface Testing Mode, then I'm going to go ahead and just do the quick, immediate-- call supers implementation-- immediately select the new tab view item.

But let's say we're going to animate, because that's the interesting part. All we've got to do is ask the new tab view item for its content view, and we've got our current content view. So we know the view we're going from that contains all of the content, all of the sub-controls, and the view we're going to.

We'll compute a union rectangle that surrounds both of them. It turns out in practice they're going to be the same size. Tab view maintains them that way. We'll calculate an image rectangle. This is where we're going to composite the image in as we're animating. And then we're going to go ahead and capture the content for the initial view that we're switching from.

And as I said, if we're doing a page curl, it looks better if we're capturing it opaque, so let's use the old-fashioned way of doing that. Here we're using locking focus on the view using init with focused view rect to capture the content. But for most cases, we want it to be transparent, so let's use that fancy new functionality. Bitmap image rep for caching display in rect to get the bitmap.

Here's clearing the bitmap. I've just got a simple function that you can refer to for doing that. I'm just going to B0 out the data. And now we'll cache the data, cache the image in there using this new view drawing redirection method. Now we switch to the new tab view item, which is going to go ahead and mark the tab view as needing display. But fortunately for us, it's not going to go ahead and do an immediate display. So we got our chance to wedge in here and do the animation before that happens.

We do the same thing with our final content view. We'll render its content, and this time we'll just do it transparent no matter what filter we're using, into a separate bitmap. So we've got our two bitmap image reps now. We'll create CI images from them both. And now I've got this method of my own that I've declared above that we'll look at briefly that's basically just a big switch statement that says, "Okay, what kind of filter do we need to create?" And pretty much it's very similar code for most of them.

They all take an input time and two input images, but some of them have--this requires a mask bitmap. So we'll use a mask bitmap or an environment map or something to composite in. So we handle each one a little specially, but for the most part, it's the same procedure. So now we've got the transition filter. We're going to go ahead and set up an animation. What I've done is I've subclassed NS animation here, done a real simple subclass so that I can get a callback from it. So I've got a tab view animation.

This is a subclass of NS animation, the sort of more abstract of the two animation classes. And we give it a duration, an animation curve. And then we'll delegate to self because I want it to call me back, the animating tab view, every time its timer fires, every time it advances in time. And then we're going to go ahead and we're going to hide the final target content view because as we're animating, we don't want it to display yet. So we're using this set hidden method that's a real convenience that was added, I think, in Panther.

And we'll start the animation. And we've set it up to be synchronous so as soon as we return from that, we can go ahead and clean up, tell the view to display itself one more time so we're sure it's in its final state. And that's that. So down here is the tab view animation, the NS animation subclass, real simple.

It overrides the set current progress method, calls supers implementation, and then sends its delegate, which is the animating tab view, a display message. And the last bit of code I'm going to show you here is real simple. We've overridden the drawRect method that's responsible for drawing the tab view.

And what we're going to do is call supers drawRect. So we're going to give NS tab view a chance to draw those tabs and draw the border. And because the final content view is hidden, and so is the initial one that's pulled out, that's all we're going to get it to draw. And now if we're animating, then we go ahead and we've got this transition filter.

We're going to get the current value from the animation object and wrap it in an NSNumber and set it as the input time of the transition filter. And then we're going to ask the transition filter for its output CI image. So it's going to allocate a new CI image that's a recipe for producing the image we want at that point in time.

And then now we're going to just composite that CI image straight in. And we've got--this is part of the bridge work that we provide in AppKit. You don't need to really get a CI context. You can go ahead and tell the output CI image, draw itself in a rectangle, and there we go.

And we've got the--so for every instant in time, we're going to redraw the whole view and just update that part that's animating. And there we go. That's all we needed to do to make this happen. And as you can see, this is the kind of thing that you probably want to use with some degree of discretion.

I would encourage you, by all means, discretion is good. But, you know, this is really just an example. I just figured, you know, I'd wire up these different things that connect nicely together, but I'm sure you will find other ways that they connect nicely together, maybe even more so, to use these kinds of capabilities to do things we haven't thought of.

I mean, once you've got a snapshot as a bitmap of a view subtree, you know, you can take that bitmap and map it onto some 3D geometry or something like that in an OpenGL view. The possibilities are pretty limitless. So if we could go back to slides, we'll wrap up now.

Slides, please. Thank you. So in conclusion, we've looked at some powerful new capabilities in Tiger, NSAnimation for bringing your views to life, ways that you can use Core Image in a Cocoa application. And again, all the details are there in the source code. It's pretty straightforward. And I urge you to review the Core Image sessions. And there's great documentation online for this. We've looked at some new ways that you can capture snapshots of view subtrees.

And we've looked at some stuff that you can do on older versions of the system, even, like using an OpenGL context with an ordinary view, creating custom views, and putting that final bit of nice polish on your view. More information. We have lots of great documentation, sample code, and resources online. Again, if you want to get the sample code for today's session, go to developer.apple.com/samplecode. Search for Reducer, and it's online there. You can just download the whole disk image.

Matthew Formica is our Cocoa and Development Tools evangelist. He can be reached at [email protected], and he can point you in the right direction if you're looking for some resources. And at this point, I'd like to invite Ali Ozer, the manager of the Cocoa Frameworks Group, and some of my other colleagues from the AtKit team up to try to answer your questions. So thank you very much.