Cocoa • 48:22
Cocoa makes it easy to handle a broad range of common 2D rendering tasks and in many cases handles its interaction with Quartz transparently. This session describes how to use classes such as NSImage, NSBezierPath, and NSAffineTransform to draw lines and curves, fill shapes, and perform common graphic transformations such as scaling and rotating.
Speaker: John Randolph
Unlisted on Apple Developer site
Transcript
This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.
Ladies and gentlemen, please welcome Senior Software Engineer John Randolph. Good morning. Thanks everyone. As the announcer mentioned, I'm a senior software engineer at Apple, which means that I'm getting close to 40. I've been using this product, Cocoa, for quite a long time. In fact, I've been using it since it was going by a number of other names. What I'm going to talk to you about today is that portion of Cocoa that's involved with actually getting onto the screen.
So we're going to talk about the classes in the Cocoa framework that are involved in drawing. We're gonna talk about how the quartz drawing model, the 2D rendering model, all those wonderful things that you saw in the core graphics sessions that were not OpenGL is reflected in the Cocoa framework. We're going to talk about how to change a coordinate space around to make it more convenient for you, and we're going to talk about images, we're going to talk about paths, we're going to talk a little bit about text.
So the Quartz 2D imaging model. You've all seen it before. You saw it in PostScript. If you're a web developer, you're starting to see it again in SVG. And the idea in the Quartz 2D imaging model is that you describe how you want to move a pen in a virtual two-dimensional space.
These moves consist of things like go here, draw a line to there, draw a curve over here, and then once you describe this path, you can take that path and fill it or you can stroke it and you can stroke it with a dashed line style and that sort of thing.
receivers all the way over there, okay. The quartz 2D imaging model also gives us alpha channel support or transparency. We can use pre-multiplied images or not pre-multiplied images, depending on what you want to do for your performance considerations. And we give you a lot of different compositing modes. We've got 14 of them.
The original Porter-Duff paper on compositing had 12. We invented two more. We care about both the source and the destination alpha, and I'll show you the benefit of that in the demo coming up. And there's a program in your developer applications examples app kit folder called Composite Lab that you can use to try out all of these different compositing modes and see which one does what you need.
Isn't that amazing? OK. So I'd like to give you some advice about writing code with Cocoa. The idea in Cocoa is that all of the parts of an application that are common to a spreadsheet and to a 3D rendering app Those parts, those common parts belong in the kit. We should do those. You shouldn't really be redoing them. So don't reinvent the wheel.
Secondly, we give you a lot of ways to change the environment you're working in. We give you delegate methods, we give you notifications, we give you a lot of ways to check what's happening and say thumbs up or thumbs down or do it different. And as a good friend of mine has said on many occasions, this is the best advice I can possibly give you for working with Cocoa.
First, see if there's an easy way to do what you want to do. A lot of common drawing tasks are taken care of by high level classes in the application kit. We've got paths, we've got colors, we've got text handling, we've got coordinate transformations, and these all have associated classes to make them easier to get at.
So if you're going to draw, the first question is what am I going to draw on? Well, actually your first question is what are you going to draw, but that's your problem. Where you draw, that's for us. A drawing destination in the application kit is an object that's able to get the drawing focus. In other words, it's an object that can get the current core graphics state. In the kit, these are objects that implement lock focus and unlock focus methods. Normally, in NSView, "You don't have to deal with locking focus.
When it's time for you to draw, that will have already been done for you." The NSView class has been described a little bit in the Cocoa sessions already, and I like to describe it as a rectangle of responsibility. It's the area in a window that's going to get the events that happen in that part of the window that it covers. So it's going to get keystrokes if it has the first responder status. It's going to get mouse events if it implements methods to react to the mouse events.
John Randolph But we're here to talk about the drawing, so I'm going to describe how NSView knows when to draw, where to draw, and how you can just say, "Here's what to draw." We also have a class in the kit called NSImage. And NSImage is also a place to draw. It's a little unusual in the kit in that it's also a thing to draw. And I'll get into that in some more detail later on.
So NSView is the class that draws, it prints, and it takes events. Every view has its own coordinate space. Views live in a hierarchy in a window. Every view has a super view or a sub view, except for the one at the very top, which is part of the kit and you don't have to deal with it.
Back here. And NSImage is a container for NSImageRep instances. As I said before, it's a thing to draw and a place to draw. You can think of NSImage as a resolution independent representation of an image. And if you're coming from the quick draw world, you used to set up your own G worlds, and in other environments you might set up a memory buffer and draw into that.
In the app kit, when you want to do off-screen drawing, when you want to assemble something in multiple steps before showing it to your users, you should create an NSImage, draw into that, and then use the image's drawing methods to get it onto the display. So now I'm going to give you my first demo. Can we go to the demo machine please? Okay. And I could go to my demo code folder or I could just go to the doc here.
So this is a fairly simple application. It's got a pair of NSImage views in it. It's got a color well, which will invoke the color panel. And it's got a pop-up menu that selects one of the 14 compositing operators that I just mentioned before. Let me go to my Handy Icons folder. And let's say, for example, I've got this eject symbol.
and I may want it in a different color. So what I'm going to do is take a color that has a transparency value and apply it Oops. Okay, now the color well is active. Take a color that has a transparency value and apply it to an existing image. Make that a little bigger.
And I don't know if you can quite see the shadow under here, but notice as I go to a completely solid color that I'm compositing over here, I still have a shadow. That's because we care about both the source and the destination alpha. Now we've got a number of different compositing operators here, and one of these is probably the one we want for whatever drawing task we have at hand. And like I said, the composited lab example can be used to test all these out and see which one you actually need for your particular situation. So, lots of them to check. So let me show you the code for this.
One thing I wanted to mention to you here is that when you're writing Cocoa code, you should think about where code actually belongs. I could have made a subclass of NSImageView that draws tinted images, that has an image and has a color and overrides the drawing methods of NSImageView. I thought that it might be convenient if every image was able to give me a tinted version of itself. So what I did in this code is I made a category of NSImage and I have two methods in the category.
This is the method that does the heavy lifting. What I'm going to do in this method is create a new image that is a tinted version of the image that's received this message. To do that, I make a rectangle for the image bounds, make a new image with the size that I've constructed up here. Lock focus on the image. That means tell Core Graphics this is where we're drawing.
Fill it with the clear color. and the image that got this message is going to composite its contents into the current drawing focus, the new image we've created. I'm going to set the color that I got from that color well, or excuse me, in this case, the color that was passed into this method.
fill using the compositing operation, which was also passed into this method and in this application. It came from that pop-up list. Unlock Focus on the image and return the image. Return it auto-released because this is not an init method, it is not a copy method. It is not an alloc method.
So anything that needs a tinted version of the image can say to some particular image, I need a tinted version of you with this compositing operator and this color. Get back to me when you've figured that out. So that is the code for the tinted image. Back to slides, please.
I really shouldn't do this from all the way over on this side. So I'm going to tell you what I did wrong in that demo. First thing is I was not very memory efficient. Every time I changed the color I was actually creating a new image and throwing away the one I had before. If I was doing this in a real live graphics application and I cared about memory footprint and cared about performance, I would actually have an image that I reused while I was moving controls around and then save that when I was happy with it.
This would also be a good time to start talking about the NSColor class. The NSColor is sort of like NSImage in that it's an abstract definition of colors, meaning anything you can use to paint an area or paint a line. It's not necessarily an RGB value. We've got a lot of different color spaces you can use. We also have pattern colors, which you would have seen in the core graphics 2D demos. To paint with a color, to make it the current color, you send it a set message.
And to create a color, there's lots of ways to get them. You can get them from a color well as I just did, or programmatically you can set an image as a color so that when you paint, that image shows through in the areas you're painting. There are a lot of named colors that are defined in the headers such as white color, red color, blue color, and so on. And color with calibrated red, green, blue, and alpha.
Let me just mention the word calibrated. Colibrated means it goes through color sync. So you can define your colors in some abstract ideal color space and you can be confident that you're going to get the same results on any machine that's got color sync installed. And then again, we have methods like control highlight color, which will tell you a value that's actually been determined at run time. So you could, for example, ask for the user's preferred control color. color.
Now, I'll mention a couple of things about NSImage. Check the size. The dimensions of the image in pixels is not the same thing as the image's size. You may have scanned that image on a very high resolution scanner that's giving you 10,000 lines per inch. If you try to draw that at the pixel dimensions, you're going to get a very small amount of it on the screen.
You can change the size of a given image without changing its pixel dimensions by sending it a set size message. You can find out what size it is by querying it with the size method. I will mention that a lot of drawing apps out there and a lot of images that you're going to get from the net are going to have size values in their headers that are wrong.
I just wanted to caution you to watch out for that, be alert, do a little bit of sanity checking before you show an image. And I'm going to mention when you draw an image with a scale, there are methods that start with composite and dissolve, and these behave differently than the methods that start with DrawInRect. So what you saw me using before to draw that image in the tinted image demo was composite to point. A thing to remember about the composite methods is that they don't respect a change in the coordinate space. The draw-in-rect methods do.
mentioned some things that are coming up in NSImage for Jaguar. First thing, something a lot of people have been asking us for for quite some time is the ability to draw images progressively. So when you're loading a large image over the net, You can make an NSImage object that will tell its delegate, "I got enough of it to know how big it's going to be.
I got enough to draw a band of pixels." And repeat that message until, "I've got the whole image." We're also adding support for multi-frame images such as animated GIFs and the like. The way that this works is we load up all the frames, put them in one long bitmap, and when you want to draw them in sequence, you'll change the frame and draw the portion that you want to for that point in time.
And now I'm going to tell you about the coordinate system. In QuickDraw, the coordinates started at the upper left corner and x values increased this way, y values increased this way. This is handy because, well, that's the way we scanned out the memory to go to the screen.
This is not the way the world of mathematics, the world of geometry does this. We are using the original coordinate system that Rene Descartes came up with. The origin is in the lower left corner. Coordinates are floating point values. Well, he didn't know what floating point meant, but that's neither here nor there. Whole number coordinates land exactly between the screen pixels initially. So that's how the coordinate system is when we hand it to you.
If you don't like it, you can change it. There are lots of ways to change the coordinate system. A very easy way to change the coordinate system for size is to use the scale unit square to size method. If you want to turn the coordinate system, rotate by angle.
If you say, okay, I want zero, zero in this coordinate system to be right smack in the middle of my view, well... Figure out where that point is, say translate origin to point, and now that's your zero zero point. We have a class in the kit called the NSAffineTransform class which will describe any sequence of scaling, translating, or rotating a coordinate space, that is any linear transformation.
Now, an NSView has two rectangles that you care about, the frame rect and the bounds rect. The frame rect tells you where you are as far as your super view is concerned. You can change it by, you can get it with the frame method, you can change it with setFrame or setFrameRotation. There are a couple of other methods for manipulating your frame rectangle. And when you change that, you're changing what portion of your super view you cover.
The one you're going to deal with more often is the bounds rectangle. And that's how your coordinate space looks from your own point of view. You use the bounds rectangle to determine where the origin of your view is. You use the bounds rectangle to determine the size of the coordinate space you're drawing in. Because when you draw, well, You're drawing in an abstract coordinate space.
Like if you have a CAD program and you say that this particular object is 10 units long and it's 5 units high, well, what does that mean? What it means depends on what the current transform it says and how that maps to the screen or how it maps to whatever device you're rendering on.
We'll talk about the NS Affine Transform class. This is an object that may look familiar to you if you were used to QuickDraw using the transformation matrix. In PostScript, you could also get at the transformation matrix. It's a little easier to get at in the AppKit because you don't really have to do any matrix math.
You don't have to know exactly what values to put where in the matrix to make a particular thing happen. We've got methods that are very much like manipulating a view's coordinate space, but they can apply to an Affine Transform. You tell it rotate by degrees, rotate by radians, translate X by and Y by or scale by. Transform operations can be combined using append and prepend transform. And it matters what order you do certain transformations in, so it's something else you want to experiment with.
So if I have an affine transform, how do I use it? Well, there's a couple of things you can do with an affine transform that I'll show you shortly in the demo. You can take a point, run it through a transform, and find out what the point is in the new coordinate space.
You can take a BezierPath and say, "Here's a transform. Make all your points go through this and get back to me when those are all done." And we can make it the current transform for the drawing context using set or concat. And now I'm going to show you the effects of a transformation.
I didn't even have to call for the demo machine. That's nice. Okay. So what I'm doing in this demo is I'm making a path with a rectangle, putting a character glyph in it, then I'm running it through a transformation and drawing it again. So the black path is the original, the red path is the path in the transform coordinate space.
And these sliders are setting the, in this case, the x scale of the transform, the y scale of the transform, the translation of the transform, and the transformation of And this will rotate the coordinate space around the origin. Oh look, forgot to hook those up. Whoops. And let me show you the code that implements this.
So first to construct the path, I go get a new path from the class, tell the path move to zero zero, append a rectangle, rectangle I've just defined up here, Append a glyph. I just happen to know that glyph number 59, since I looked it up in the Unicode table, gives me a nice big Mac OS X X that I can use from the Helvetica font.
Once created this path, in my drawRect method, well, I check whether I've already got the path in my instance variable, go and get it if I don't. If I don't have the transformed path, I go and get the transformed path from, oh goodness, where did I put that? Oh, here we are.
from ConstructTransformPath. What I'm doing in this method is I make a new AffineTransform using the convenience method from the class. I tell it I'm going to rotate by this value that I got from the user interface. I'm going to scale by this value that I also got from the user interface from the slider.
I'm going to translate and then Okay, well this will create the path and return it. Every time I come through the UI saying that I need to change the path, I have logic here to check for, Whether the path is in existence, I'm being a little economical here by reusing the transform path every time I want to change it. And in the DrawRect method, I set the white color, fill up my bounds, set the black color, set the untransformed path, set the red color, and stroke the transformed path.
What I did wrong in that demo? Well, I was drawing too much. To draw the white background, I was actually filling my entire bounds. I should have just been drawing what was handed to me in the drawRect method, the one parameter saying, "I've got this rect. I need you to refill." I could also only have drawn the parts that changed.
I can find out what the bounds are of those paths that I'm drawing. I can tell the app kit that I'm only going to need to change this rectangle that the transformed path lies inside of. also made lame excuses because it's just a demo and I don't have to be the best coding expert in the demo. I'm going to go to the next demo here and show you that I can transform images.
Just as readily as I can transform paths. Let me get some of this stuff out of my way. So let's say I take an image here. And how many of you ever wrote a raster rotation? Lot of work, wasn't it? Well, it's not a lot of work anymore.
All I'm doing here is I'm telling this image to composite itself in a rect and that rect is in a rotated coordinate space. Now one thing that we can do in NSImage, I mean people will tell me from time to time, "My God, John, you know this is way too easy. You know, I'm a hardcore pointer chasing, bit flipping, image processing guy." How can I get at all the pixels in that image? Well, how do I compute the values for every point in the bitmap? Well, we can do things like that. Here's one that I did.
But I didn't want to do all the work to rotate that raster, so I just did the work that generates the gradient in a given 256 by 256 image. Let me show you the code that you can use if you really want to be a hardcore pointer chasing, bit flipping image processing expert. Here we are in transformed image.
I wanted the ability for NSImage to give me a gradient, so I wrote this method called Pretty Gradient for the WWDC demo. Pretty much like I did in the tinted image, I'm creating a new one. I'm giving it the size that I've defined up here, 256 by 256. Creating a bitmap image rep. As I mentioned, an NSImage object is a container for image reps.
Creating one by telling bitmap image rep Alec, "In it with data planes, I'm passing it no pointer here," meaning you make the memory, you give it back to me. Telling its size, I want 8-bit samples, I want 4 samples per pixel. I'm going to create it in the calibrated RGB color space, so I'm going to be able to take advantage of color Bytes per row, you figure it out. Bits per pixel, 32, I figured that out for you.
Check this out. I've got a pointer to the raw bits. Again, I'm not dropping down to core graphics here. This is plain old C bit twiddling, pointer chasing, and then I'm going to Yes, you can directly get at the pixels this way. And since I put this in an image rep and put that in an NSImage, I get the rotation for free. I don't have to calculate the image rotation. And again, returning it as an auto-released instance because This is a convenience constructor. It's not an alloc method. It's not a copy method. It's not a retain.
Okay, this is probably not the first lie I've ever told any of you, but I'm just going to gloss over what I may have done wrong in that demo. So now I'm going to talk a bit about BezierPath and go get some of that water. So in the core graphic sessions they would have told you about the CGPath and if you were a PostScript programmer you would have constructed paths, you would have written yourself little routines to draw arrow heads and character strings and so on, all those good things.
What we've given you in the app kit is a path that encapsulates that. The BezierPath represents the PostScript scalable vector graphics, core graphics, path, stroke, fill drawing model. We have path construction methods such as move to, line to, curve to, things you'd be familiar with if you read the Adobe Redbook or the Adobe Greenbook back in the day. We can also combine BezierPaths.
This was something that wasn't actually all that convenient to do in PostScript. So if you've got a path that describes a character outline and an image, you can do that. Another path that describes some other figure you've got. Well, with BezierPath you can combine them and get one path.
We have path attributes, of course. We've got line join, line width, miter limit, flatness, which you'll all be familiar with if you've written page layout programs and had to deal with them. And paths, of course, can be drawn. They can be filled with the current color of the drawing context. They can be stroked with the current color of the drawing context and the current line style.
We give you convenience methods. These are class methods. So the idea in BezierPath I think was basically that this is where you look for vector drawing. So maybe you're going to construct a path, maybe you just need to draw a line. If you just need to draw a line, you use the class method that says stroke line from a point to another point. Now, I like to add methods to existing AppKit classes. I find it convenient.
I like to To my mind, it makes my code a little more reusable. In the kit, we've given you methods like BezierPathWithOvalInRect, BezierPathWithRect, BezierPathWithGlyph, and things like this. But I want something a little more artistic than that. I'm going to add a method to BezierPath to draw something that we often see in graphics textbooks.
Isn't that pretty? Okay. It is, but I'm done with it. Now isn't that beautiful? I've got a polygon that I'm drawing right in the middle of the view. I've transformed the coordinate space to put it in the middle of the view. I can control the number of sides of the polygon as you can clearly see. I control the rotation of the polygon again as you can clearly see I can change its color.
There, now that polygon is showing in glorious red. Oh, I'm sorry, I can also change the coordinate space so you can actually see the polygon that I'm drawing. You can make it more sides and it approaches a circle and less sides and it's not very round anymore. I can change its line width. This is just one of the drawing attributes of the current, actually the line width is an attribute of the path when you draw it. And we can get artistic. Now here's something I want to warn you about.
The cost of stroking a path depends mostly on how many times the path crosses itself. To do the right thing for rendering this path so that all of these points don't end up darker than they should be because we're drawing over the same area twice, we've got to figure out each of these intersections before we send it to the rasterizer. And as I go up, You are going to see this getting noticeably slower.
It's not going to break, but it is going to slow down. This would go a lot faster if I was drawing individual vectors. And they'll be telling you about that in the graphics and imaging performance talk a little later on. You might actually see this image on one of their slides. Let me point something else out here.
As I move the size slider, The reason that that string art view isn't just going outside the window and clobbering everything else is because of the clip path that was set up for me when lock focus was called on this view. I can add to the clip path if I wanted to draw this say behind a big blue X because I like string art and I like Mac OS X. I can add to the clip path by defining a path and telling that path add yourself to the current clipping path.
I'll point out another thing here. Notice that as I increase the line width, I've got nice line joints. When I go to the meshed mode, see these gaps? That's a consequence of the sequence that I was adding these line segments. Where I'm creating the polygon when I'm just drawing the perimeter, I'm actually going about it slightly differently than when I'm drawing the meshed polygon. And let me show you the code for that right now.
Again, I like adding categories to existing AppKit classes. I want BesierPath to be able to do polygons for me. So I defined a category on BesierPath called polygons. I tell it, give me a polygon with sides. I will draw this polygon in the unit circle because it's so convenient for me to just change my coordinate space to draw it at whatever size I need.
And the math is pretty straightforward here. Make a circle. has a loop that will take a step out of the circle, go to the cosine and sine of the current angle. I call line to point, line to point, line to point, line to point until I'm done with the loop, tell the path to close the path. The fact that all of these line 2s happen in sequence means that the BezierPath knows that it should apply its current line joint.
Where I do the meshed polygon, I'm going about it in a slightly different way. And you can see that the sequence is move two, line two, move two, line two. Now the fact that I've moved and then drawn the line means that it can't simply outguess me as far as line joins go. So that's why you saw those little gaps when I'm in the mesh mode. And of course this is a Cocoa app and it's so easy to do things like this that I do.
Yell when you think you like the colors. Why don't we put red on blue? That'll make all of our eyes really bleed, won't it? I haven't played with this projector in my--ooh, ooh, that's harsh. Okay. didn't mean to build that. All right, and let's go back to the slides, please.
What I was drawing wrong this time. Again, I was drawing inefficiently. They'll tell you more about this in the graphics and imaging performance talk after lunch. I was clearing and redrawing the entire bounds. I should have just cleared the rectangle that was handed to me in DrawRect. I did not reuse the BezierPath.
I was getting a new one every time. I should point out it's not actually necessary to throw BezierPaths away. They are mutable. You can change them. If you have a sequence of points that you want to draw, you can always send a remove all points message to a BezierPath and you won't be doing a round trip to the memory pool. As before, I made lame excuses because it's just a demo.
Thanks for remembering when you're using the BezierPath, the rasterizer charges by the intersection. I will also mention many people have asked about the, you know, if you draw a line, a vertical line from 0, 0 to 0, 10, you end up with a fuzzy gray line. Well, I'm not sure The fact is our coordinate system, the integer values of the coordinate system land exactly between the screen pixels when we first hand you a transformation, the current device transformation. You can change this to make sure that you get a solid black line. You can bump the coordinate system just a bit, but I want to caution you about that because We really want to head for a time where we're completely resolution independent.
I mean when we first brought out the Macintosh we had 72 DPI, all displays were 72 DPI. The new titanium powerbook, I think we just shipped, it's 104 dpi. And higher resolution displays clearly are on the way. I'm not sure when we're all going to have displays that have the same resolution as the original laser writer.
But all of these little tweaks that you might be doing to deal with today's device resolution, check and make sure you really need to do them. In particular, right now, check whether you're drawing for the screen or for the printer. You can ask the graphics context whether you're drawing to the screen. And if you're not, turn off your little tweaks.
which brings me to the graphic state. So if you're an old PostScript hacker, if you're looking for gSave and gRestore, this is where they are. They're in save graphic state and restore graphic state. We can turn off the anti-aliasing if you don't need it for your particular rendering situation. And you can test for whether you're drawing to the screen and so on. I urge you to read the documentation on the graphic state class. It can be convenient for you.
You can make a lot of changes to a graphic state. You can call save graphic state, make a lot of other changes and pop back to your original conditions, your previous coordinate system, your previous clipping path. Let's see, what else is in the graphic state? There's coordinates, clipping path, your current color and so on. And you can basically keep all of these elements on a stack very conveniently with the graphic state We'll also point out that there are a lot of functions in the AppKit.
Not all Cocoa API is Objective-C API. And these C functions that we give you, such as rectfill, rectfill list, and so on, the rest of the ones I have on the slide there, these are probably the fastest way you have to draw vertical or horizontal lines. They're very highly optimized because we're using them in a lot of places in the kit ourselves.
We also have geometry convenience methods. DivideRect will take a rectangle and give you back two. We can test for whether one rectangle intersects another, whether a point is in a rectangle, testing for equivalence. There's a lot of things there to get you elements of rectangles. And I'd just like to say if you think it ought to be there, if you think it's something a lot of people have probably needed to this point, look for it. It's probably there. I see a lot of places where people have, you know, are referring to elements of a rectangle like myrect.size.width. They could have just used nswidth to get it.
We'll mention also the app kit adds some methods to NSString. There's draw at point. It's really quite simple to just put a label somewhere in your view. If I wanted to draw a little coordinate pair and show you where the origin was in my string art view, probably another two or three lines of code.
I'm not going to go much into text here because there's a whole session on that left to go. I certainly recommend that you go to that session. The Cocoa text system is very powerful. It does a lot of really cool things that are beyond the scope of this talk.
I'll mention another class here, the NSCell class. If you went to the controls talk, they would have shown you how to do the clock cell. Most of the controls in the app kit are just containers for a cell. This is why it's so easy for us to like turn a slider into a matrix of sliders. We just change from a slider, an NSSlider, which is a view, to an NSMatrix, which is another view that's got a number of cells in it. A cell is something that draws in a view. A cell is also typically a responder.
And you'll use a cell where you don't want the overhead of an NSView. A cell doesn't need its own coordinate space. We've got cell classes already for drawing text and for drawing images and if you have a view where you're going to need to let someone edit a little bit of text, cell is a very convenient way to do that.
If you just want to draw an image, you don't want to bother with compositing an NSImage, you can get a cell object, tell it, get this image, give it an image by name, and give it a path that you got from an open panel. Cells can save you a lot of work. And I would like to urge you to check out the documentation on NSCell. Use them where you can.
So you can use a cell for drawing lines, for editing text. You can reuse cells. In fact, you can use a cell multiple times in the same draw method. In fact, in our table view, When you've got 10,000 things to draw, well, it would really be expensive to have 10,000 subviews.
So what we do in the table view is we use the same cell for every column. We just change the text that it's going to draw and say, "Draw yourself here. Change your text, draw yourself here. Change your text, draw yourself here." It's a very powerful design approach and would certainly be worth your while to check out how to use those.
A cell is often a good starting point for your own custom drawing. And in a cell class, it's very handy sometimes to hijack the inherited drawing behavior. And I'm going to go back to a demo you just saw before and show you something about the code in there. Okay, let's go back to my transformed image code. Close this project. Wrong project, that's a tinted image. Transformed image, here we are.
Now first I'll show you my transformed image view that was drawing the rotated image is an NSImage view. That means I didn't have to write the code that accepts dragged images from the finder. It means I didn't have to write the code that drew the bezel around the image. All I did with the transformed image view is I've added a couple of instance variables, the rotation and the scale. I added a method to show the gradient for the splash factor. And let's see, going back to the implementation of transformed image view.
Here's a place where I'm lying to an object in the kit. I am telling the cell that it had, "You don't have an image. Draw yourself." That gets me the bezel. It doesn't leave the image un-rotated for me to have to deal with. Changing my coordinates base. Let's see. Building the destination rect of where to draw the image. So I lie to the cell.
I say you don't have an image. I make it draw itself. I figure out where I want to draw the image. I draw the image there, set the bounds rotation back, Tell the cell, oh, by the way, you do have an image after all, but this won't make it get drawn again.
So the upshot is by just using the existing machinery of the cell in the NSImageView, I've saved myself the work of having to deal with storing an image, having to deal with accepting image drags, or having to deal with drawing that bezel. If there's some class in the kit that's already drawing some of what you need, use that existing drawing behavior and then modify it. So, let's, back to the slides please.
Now I've left us a pretty long time for the Q&A because somehow I managed to rope most of the AppKit team in here. And let me just sum up here and say, We saw how to draw using the classes in the Cocoa Framework, how to get some fairly sophisticated effects with not a whole lot of code, and the whole idea of Cocoa coding, use the least amount of code you possibly can.
So there's the point of the talk. Do the simple things the simple way, and if you want to do the hard things, you can. There were many complaints about the Cocoa documentation over the last year and they were fair, but I'm happy to say that most of this is pretty much dealt with now. We've got a lot of new documentation that's topic oriented. I urge you to go have a look at that documentation. There's an excellent section on the view hierarchy and on drawing in Cocoa in general. It should be on all your Jaguar disks.
For more reading, Learning Cocoa, I was one of the technical reviewers on Building Cocoa Applications. This was a wonderful book. It's got a lot more detail on all of the things that I just talked about today. You all got a free copy of it, I presume? and besides the Garfinkel-Mahoney book, the developer documentation available on the web and available in the help folders on the systems you have. And if you want to go to a class about this, you can contact us at train.apple.com. So here's the road map which we've been through nearly all of it. Two left. We've got the Cocoa Text session. There is a lot in Cocoa Text.
I don't know how I can say it more than that. Japanese text input, you get it for free. A tremendous amount of sophisticated typography functionality, you get it for free. It's all described in section 306. And we have the feedback forum for Cocoa coming up today at 5:00. So I hope you'll catch those sessions as well. We're interested in what you have to say about these kits.
Who to contact? The Cocoa Evangelist is my colleague Heather Hickman sitting right over there. Cocoa Feedback, we do read this mail at [email protected], and there are a number of excellent mailing lists available at mail.apple.com. The one most pertinent to this talk is the CocoDev list. We've got quite a lot of people on there, people who are just beginners, people who have been doing this for ten years as I have, and I will also mention that a lot of the people who work on the kit read that list frequently.