Mac • 1:06:03
Often, the difference between an amazing application and a merely adequate one lies in the level of polish applied to the user interface. Learn how to take a functional Interface Builder project and add the subtle details that make the user experience feel intuitive and look more attractive. Find out how to add custom drawing and controls to get the exact effect desired, and even how to add Core Animation to make your application shine.
Speakers: Joar Wingfors, Ron Lue-Sang
Unlisted on Apple Developer site
Downloads from Apple
Transcript
This transcript has potential transcription errors. We are working on an improved version.
[Joar Wingfors]
Good afternoon everyone, and welcome to this session on State of the Art Cocoa, user interface development. My name is Joar Wingfors, and I'll be giving this session together with my colleague Ron Lue-Sang. And I also want to get a quick shout out to Scott Stevenson of Apple who has helped us to prepare these slides and the sample code. The Mac platform has always been defined, at least in part by its attention to usability and user interface design. And as a result we have a lot of fantastic applications on this platform for, from Apple and from View, [phonetic] our third party developers.
For many of these applications the user experience is one of the most, one of the most important features that these applications are recognized by or, or compete with. And Apple provides a rich set of tools and technologies that helps us write these fantastic graphical applications. But if you're new to the platform, or if you're new to user interface development in general, it can be a bit difficult to know where to get started, and that's what this session is all about.
I also want to mention that this session we are going to expect that you have a certain level of familiarity with Xcode, Objective-C, and Interface Builder, and not go in too much of detail on those topics. So Ron and I will take you on this journey through the world of Cocoa UI development. And while we won't be able to cover all of it, we hope to cover enough that by the end of this session you know enough to be able to get around on your own.
And on this journey we're going to make 4 stops. I'm going to start out by talk about how to get started using Interface Builder. On the second stop I will talk about the fundamentals of using Cocoa for drawing. After that Ron will come on stage and talk about how to create custom controls and how to integrate your custom controls in the Cocoa UI libraries. And last, Ron will talk a bit about how to use behaviors and animations to make your user interfaces come alive.
So while this session will be mostly be about the more hands on part of creating, or viewing UI development, the successful outcome of that, of those efforts will, to a large degree, be determined by a lot of things that have come before you actually end up in your implementation phase.
And to, to achieve really outstanding results, like the examples on the, one of the earlier slides, you need to allow for your UI designers to affect both the requirements and all of the input parameters for your project, as well as the output parameters of your projects, the vision and the, and the, and the look and the functionality.
You need to have, allow your user interface designers to play like, play, have an equal part to say as your software designers for that final result. Apple provides a set of human interface guidelines that can support and guide your UI development efforts. But it's important to note that these are not a set of rules, these are a set of guidelines, and should not be, and you shouldn't think of them as something that limits the, the creativity of your UI designers on the Mac.
So when you do user interface developments on the Mac you have two things to help you. You have the Cocoa UL libraries on the one hand, and you have Interface Builder, our visual design tool, tool on the other. From time to time I hear developers who are new to our platform they ask about how to sidestep Interface Builder entirely and how to build all of your user interfaces in code. And while this is certainly absolutely possible, and you know, to some extent it could be an interesting intellectual exercise and learning experience possibly, we here at Apple, and Mac developers in general, we don't do this.
Instead we're relying on user Interface Builder. And why do we do that? You've probably heard the saying that a good developer is a lazy developer. And what we mean by that is that good developers know that the code that you don't have to write for yourself is code that you don't have to debug or maintain, probably for years to come. And this is where Interface Builder really excels.
If you use Interface Builder to create the, the bulk of your UI, all of the parts that make sense, you end up being so very much more productive in the end. So let's say we have a company and we've set up to, set up to create a light table application And the light table is an application allows, that allows users to drag in photos and images, and arrange them together to see how they work out.
We have arrived at a point where we have some design documents and art resources, and, and where do we go from there? So what I was, would do is that it would start out by trying to identify the major components of, of the application, or in this case the application main window.
So over here on the far left we have what we call a source list. And the source list lists all of the projects that the user has created in our light table application. You can see that for the project we have a nice, a nice thumbnail, and a title, and a count for the number of images in the project. Beneath the source list we have a utility bar that groups a number of controls. Over here it's the light table itself, the main canvas of our application. And finally there is a split view that divides the source list from the light table.
[ silence ]
[Joar Wingfors]
In addition to being allowed to reposition and resize the photos on the canvas, the users, our design document also calls for allowing the users to flip over the photos and configure some display characteristics for the photos by using a special set of controls in the back.
And you can, and you might notice that just like with some of the other controls in this, and some of the other UI in this application, the controls have a bit of a custom theme here on the backside of our photos. So now that we have a basic understanding of the functionality and the UI for our, our application, lets jump into our first demo and look a bit at how you would bring this up using Interface Builder, and just how far we could get using Interface, Interface Builder in standard controls.
[ silence ]
[Joar Wingfors]
So just like in a good cooking show I've cheated a bit here, and but I'm started. I created a simple Cocoa application with a single window controller, and I've spent a couple of minutes just dragging in standard controls from the Interface Builder library to create the basic shape of the window. You can see that it is recognizable. And if I run this application just from IB I have some buttons down here, I have my slider, split view works. Not everything is working perfectly.
For example, you know this background behind this slider shouldn't be clickable. I sort of tried to make something that looked like a utility bar by putting gradient button behind here, but that isn't really working out perfectly. If I were to spend just a little bit more time using Interface Builder
[ silence ]
[Joar Wingfors]
This is still just using the standard stuff from Interface Builder I, I could get this far. I, I have the basic shape of the window up. I've cheated a bit and I've added this very simple custom view to draw the background of, of the light table, but that's about it.
You have the source list appearance with the special selection colors. I even have some basic data from my application provided by data bindings that I can set up in Interface Builder. But it's clear that we still have some ways to go. I don't have the, sort of the darker theme that we wanted for the glass bar, the utility bar at the bottom of the source list.
We don't have all of the photos, because the photos would have to be created problematically, so I can't set them up in IB. So all of that would have to be added by customizations in our application. And Ron will pick up where I left off, where I left off this demo application, and make all of those customizations to make this demo app reach the final design that we saw from our design documents.
[ silence ]
[Joar Wingfors]
But before Ron does that I'm going to switch back to slides and talk to you a little bit about the, talk to you about Cocoa and Mac OS X as a drawing platform, so that you have the understanding of the drawing technologies that we need to create these custom controls.
This will be a, a overview of the technologies that Cocoa provides for a drawing, but it will be reasonably complete. And I am going to try to focus in on some specific concepts that we use in Cocoa for drawing to make sure that we cover them in a good way.
Now everything that we see on screen lives in a window. The tic tac toe game up here, you know, that application window is clearly a window, but so is the menu bar, the dock, and even the desktop picture Similarly, everything that you see in your application windows, your buttons, your text fields, your scrollers, all of that is of a type of object called views.
Views provide event handling and drawing for a particular rectangular area inside of the window. In this case we have the background view here that draws the checkered background for this, for this game. You can add subviews to views. And subviews added to a superview, they assume some of those responsibilities, the drawing and event handling for the area that they cover in their superview, the game piece in this example. In this day, in this way views are arranged to perform a single view hierarchy inside of the window.
[ silence ]
[Joar Wingfors]
In order for views to do their drawing and event handling, they need to be able to refer to the casians [phonetic] inside of their canvas, to do that they need to have a coordinate system. And the coordinate system for views are, have their origin in the lower left corner of the view, like in this example.
Some views have what is called a flip coordinate system, where the origin of the coordinate systems in the top left corner. And we do that when the contents of the view naturally flows from the top left corner and down. That makes it easier to lay out the contents.
An, an example of that would be for views that manages to lay out a text. The position and size of a subview inside of its superview is called the frame The data type for the frame is a simple C structure called NSRect, which is in turn composed by an NSPoint C structure that contains the X and Y positions, and an NSSize C structure that contains the widths and the height of the subview.
[ silence ]
[Joar Wingfors]
And to complete this picture just like the superview had an internal coordinate system, so does all of the subviews in turn, of course.
[ silence ]
[Joar Wingfors]
So while Cocoa is an object oriented framework, not everything in Cocoa is an object. The data types and functions used to, to work with and describe the geometry for views is an example of that. There, there are a number of convenience, convenient that you can use to work these data types. And they might be somewhat easy to overlook in the documentation, but you should make sure to familiarize yourself with these functions, because they make it much, much less error prone and much more convenient to work with these data types.
And among those functions there are also a number of functions that provide basic drawing; I'm going to call out two of those. NSFrameRect and NSRectFill, they both take an NSRect parameter and they either, they either outline the shape of that Rect or fill that Rect with a single color respectively.
Moving on to the object oriented parts of the API, starting out with gradient, paths, images, and text. NSGradient is a relatively new addition to the Cocoa APIs. You can create linear gradients or radial gradients using two, as in this case, or more color stops. NSBezierPath provides for the supports creating simple or, or very complex shapes. And we describe these shapes, or we create these shapes using path segments that can be either straight, as in these, as in this example, or curved.
And besides allowing you to describe your, your shapes or objects, NSBezierPath also supports drawing. And you can use the path class to both stroke the outline of a path or to fill it with single colors. What is more interesting though is that BezierPath supports flipping other drawing operations, or masking them. If I were to use this star-shaped path as a clip path, and then draw a, a standard rectangular image, I would end up with something like this.
And this is a very useful capability that you should all think about; we use it all the time. Images in turn, you can create images from files or from data in your application, and besides the obvious, the ability to draw an image to a window where the window is the, the drawing destination, you can also use images as drawing destinations on their own, where subsequent drawing operations get rendered into that image.
NSImage provides API for loading images, and drawing images, and other, and other things too. But they don't actually handle the image data themselves. When an image is asked to load some image data, it turns around and creates an instance of NSImageRep. The particular subclass of ImageRep created depends on the type of image data that you ask NSImage to load. If you were to ask NSImage to load a PNG or JPEG file, some type of bitmap image format, NSImage would create an NSBitmapImageRep internally. Similarly if you were to try to load a PDF image it would create an NSPFImageRep.
When you do custom drawing for a view, you can take this to 1 of 2 approaches. You could either use all of the technologies that I'm outlining now using colors, and paths, and gradients, and you can do all of the drawing problematically at one time, or you could have your designers do all of that hard work for you and supply you with images that you ship with your application that you load at one time and, and draw at one time.
I guess you could do a bit of both as well, right? But two approaches. If you have something like the button that we have on this screen here where the button, button needs to be able to resize horizontally, for example it will fit, you know, the contents of the button, like the title. This requirement doesn't preclude you from using prerendered images.
You can slice up your images, and if you were to use the 3 and 9 part drawing functions that Cocoa provides, it's very easy to then, at one time draw these images at the appropriate width. Using these functions take away a lot of the positioning problems and sizing problems that would otherwise be involved by trying to draw something like this by doing it manually.
It's very easy to draw simple strings of text in Cocoa using the attributed string class. NSAttributedString provides you with control over all of the properties that you would typically like to manipulate or control when drawing text. You can set the font to use, you can set the alignment of the text, you can even specify the type of truncation markers to automatically get inserted in your text when the text is too wide to fit the allotted space.
NSAttributedString also provides you with a measurement, with the measurements that you need on, of your string in order to be able to position it before you draw it, so the width and the height of the string. Moving on to colors and shadows, and the reason that I separated out these two object, objects is that they work rather different, differently from the previous four.
And I want to talk a bit extra about those why them, why they work differently and how they work differently. Here I create a red color by specify the red, green, and blue components, and the opacity for the color. But unlike the earlier examples we don't draw colors directly, and neither do we typically pass colors as parameters that are drawing functions, instead, instead we sect the colors in a shared context.
I'll get back to this shared context in a minute. This is how you would create a shadow by specifying the color of the shadow and the offset from the object casting the shadow. And just like for colors we don't draw the shadows ourselves or explicitly, instead we, again we set the shadow and its shared context where it affects subsequent drawing operations. Even something as complex as this text drawing here would get the shadow applied.
So the shape context is represented in Cocoa by the NSGraphicsContext class. And the graphics context is a fundamental part of the drawing infrastructure, not only in Cocoa, but on Mac OS X. The graphics context describes the drawing destination; it could be the window, or the bitmap image that you are drawing to. But it also provides a multitude of configuration options that affect your drawing.
We already talked about the colors and the shadows that are set in the context. The example of using a NSBezierPath to clip your drawing, that functionality, is also supported by the clipping radiant support provided by the graphics context. In addition to those examples there are font attributes, and path attributes, and the list goes on. Since this is a shared context, like a global thing, NSGraphicsContext provide for a push and pop model to scope your local configuration changes.
You access this push and pop model by using the save and restore methods available on the graphics context. Note though that for the most part when you provide, when you do your custom drawing in Cocoa in a subclass of NSView or something like that, for the most part you don't have to manage the context yourself; you don't have to create it, you don't have to customize it for the most part, you don't have to, for the most part, call, save, and restore. All of that is done for you. If you need, if you need to do those things you can, but for many simple cases you wouldn't even have to.
Now that you know a bit more about how to do drawings, we need to think a little bit about when it is appropriate to draw. The first half of the answer to that question is easy, we need to, we need to draw whenever, whenever state that affects our visual appearance changes. But the, there is a catch here.
And that is that when the state of our application changes oftentimes many views would be updated at the same time. And not, not only many, many views, but also many properties per view. And there are, it is for many reason inefficient and undesirable to try to push all of those individual display updates to screen individually as soon as you get the, the, the, the change in the state. So to take care of that Cocoa provides a drawing model that is coalesced.
Where all of these drawing updates are done at the same time. So this is the basic, the basic sequence of events for how this takes place. So your view is handling some event that changes its state in a way that that affects the visual appearance of the view. At this point the view will flag itself as needing redisplay. Sometime later, typically at the end of the event loop, Cocoa will call back to the view all of, all of the views that have marked themselves with needing redisplay, and ask the views to draw.
The guides that are provided on NSView to support this is setNeedsDisplay and drawRect. Again, if you're only using standard controls and standard properties in these controls you don't have to do this for yourself. If you have a standard NS button and change the title, the button will mark itself as needing redisplayed, so it happens automatically. But if you create a subclass of NSView or one of the controls, and if you add additional properties that affects your visual appearance, you're on the hook for calling setNeedsDisplay when the state of those properties changes.
drawRect, so, setNeedsDisplay is something that you need to call at the appropriate time, drawRect on the other hand is a call back method that you provide an implementation for in your subclass, and that Cocoa calls, but that you never have to call directly. Remember this part of our design document. Take a closer look at this custom button here on the back of one of our photo tiles.
For my second demo I'm going to take a look at how we provided the custom drawing for this button. And in the interest of saving on time I'm not going to show you the code for this, but the code is about yea big, it's less than a page, and it's done all using the techniques about, talked about earlier in this session. So whenever I start to do some kind of new custom control, the first thing that I would typically do is that I would throw in a call to NSFrameRect and ask it to draw just a red border on my view.
And this is for just an early and quick and dirty standard to check to make sure that, you know, I have the view set up, it, it's roughly, you know, in the UI where it's expected to be. It's sort of similar to printer-style debugging and code, right? And it's also somewhat useful troubleshooting and debugging functionality for its system custom controls, if you want to see what, what's wrong.
The first thing I would do for when creating this button is to create a path that describes the rounded rectangular outline of the button. And I, then I would set this path to be a clipping path for all of my subsequent drawing operations. You can't see me doing that, but as soon as I draw the first gradient background of the button you can see the clipping path taking effect.
So when I, when I draw my gradient, this dark to darker gradient for my background, this is, I'm drawing into your rectangular area. But since I'm using the clipping path I get this now, nice rounded rectangular outline. And there's a very convenient method NSBezierPath for creating these rounded rectangular paths My second drawing path is to draw the highlight sheen on the top half of the button.
And I'm drawing that using a second gradient that is much lighter. Now that I have all of, all of the background for my button I can draw the border around the button. And that'll be very easy, since I already have a path for the outline of the button.
And I can reuse the same path that I'm using to clip drawings to, to, to, and stroke that path to try the border. So the final thing I need to do here is just to create an attributed string and to draw the text. And while the text here, the title says done, I'm not quite done yet, seeing the design calls for adding a shadow.
So I add the call to creating the shadow and here we are. So while this, while this mostly looks like the button that we have in our design, since I just created this as a standard NSView subclass it doesn't actually have any of the behavior that we would expect from buttons, from a button. I can't click it. It also doesn't have any of the important accessibility properties that a button subclass would have.
A blind user couldn't interact with this, because this new subclass doesn't expose a title property that they could interact with. So in order for you to learn more about how to integrate your custom drawing into Cocoa, and to do, actually, talk more about how to creating custom controls I'm going to ask Ron to come onstage and take over. Thank you.
[Ron Lue-Sang]
Hello everyone, my name is Ron Lue-Sang, and I'm going to talk a little bit about how we can create custom controls that will fit the design of our application. And then go on to talking about adding custom behaviors, and even a little animation to really breathe life into the application and make it fun for our users to use. Now the place where I want to start out is actually where Joar finished off, which is with a basic skeleton application.
A good prototype with all the pieces mostly laid out, but not completely functional yet. Now there are a few things that are left. We have to actually implement this canvas where the user can draw, drag photos into. There's the source list area where you can pick projects. And then there's the utility bar at the bottom.
Those still have to be finished, even though we have pieces stubbed out. Now this is just a basic process that we use in order to get from start to finish. And it's all about making these controls and making sure that they fit the design that we started out with. That's really the focus here, is having a process for making our controls fit our design. Now we've already broken down the pieces.
I want to focus first on the utility bar that we have at the bottom. You know, what approach should we take for making that utility bar real? Well we, we saw that if we were to take just a gradient bar, a gradient button as our background and slap some pieces on top, it doesn't' work quite right.
You know, if we were to just try to take a gradient button and, and fake something in. The same would be true if we were to try and snag a segmented control, try and piece some parts in like that. You're looking at the options that we have. There's no built, prebuilt utility bar available in Cocoa. Nothing we can just drag out of IB. So our options really come down to building our own, and that's fine.
All we have to do is take out the placeholder pieces we have in our Nib and replace them all with just one simple custom view. And then in IB we can set the class name for our custom view. And that will ensure that when this Nib gets loaded that custom view will be replaced by an instance of our utility bar class. So it makes it really easy to add our custom controls to the Nib and not have to add any fiddly code. Back to the actual implementation of our utility bar class though, the way I would approach this is to just use composition.
I mean Cocoa is an object oriented language framework. And, you know, composition is a very object oriented thing, approach. We have just, you know, our segmented our slider, a few buttons, an image over on the far right, and this gradient background. We could implement this utility bar just by subclassing NSView like Joar did for his custom button, and implement drawRect to draw this gradient background using NSGradient. Use an NSImage to composite a little grabber image area out on the right, and then add a couple of subviews for the buttons and the slider. And using composition, just using this approach, we could implement this utility bar in a pretty straightforward way.
The code here isn't really that interesting. What I want to focus on is the approach for making controls fit your design. Start out with something that's prebuilt and see if you can tweak it. If, if there's not enough, if there isn't anything you can do to tweak this control, then certainly look at using delegate methods that might be available on this control to customize it further. And barring that, take a look at overriding some, some existing methods on an existing control. So it's really just a process of starting out with something that's prebuilt, and customizing as you go.
And only if all that isn't sufficient, take a look at making a new one. And don't be afraid to make a new one, but don't jump in too early assuming that you'll have to go back up to subclassing NSView for everything. So with this approach in mind let's take a look at another control that we still have to work on. We have our outline view, and it has some of the right colors that we want, but it's still far away from being finished, from being a real source list.
You know, for example, we want to hide the disclosure triangle next to that little project group in that first row. We have to draw the custom image, and text, and photo count all in one row in our, in our outline view. And there are actually some selection rules for making something a real source list.
For instance, that group at the top shouldn't be selectable. And you're only supposed to be able to select one thing at a time, no more, no less. So there's a, a bunch of things we have left to do. And we could get all of this done by using OutlineView delegate methods.
So this is another one of those places where after we've tweaked some setting in IB or in code, we can use delegation to get a lot farther customizing these existing controls. An example here, something that's new in Snow Leopard by the way, is being able to hid the expansion triangle for a given row in the OutlineView just using a delegate call back.
From there though we still have some other things we need to do. We haven't touched on the custom drawing in each of these rows, like drawing the text and the icon together and having custom editing rules, tooltip placement, a lot of these things, it turns out, aren't customizable directly from OutlineView. But don't be disheartened, it's not something that we, we now have to go back up and subclass NSView and rebuild everything from scratch. We wouldn't want to do that. In order to, to get the rest of this work done, I want to talk first about cells.
Now if we were to take a much simpler control, stepping away from OutlineView, take a very simple control, the text field. NSTextField and the way it draws, if you were to look at it, NSTextField is simply an NS control that uses an NSTextField cell for all of its drawing.
TextField is just a control that uses a TextFieldCell for all of its drawings. But if you were to look at where TextField and TextFieldCell are in the class inheritance hierarchy, it would be worlds apart. Right? We'd start off at NSObject, and below that we have NSResponder. Now NSResponder is our base class for everything in Cocoa that responds to user events, things like Windows, actually Window Controller, the NS application itself, and views, and from NSViews to send NSControl.
NSControl is the base class for everything that we're used to manipulating in our application UI. Things like, buttons, and sliders, and text fields. So this is where TextField lives in the class inheritance hierarchy. That's two. But if you were to look for NSCell it's all the way out here on its own, just directly from NSObject.
It's a very lightweight class that's really just interested in drawing and some user event handling support. And I said that TextField's use TextFieldCells for their drawing. Well, every TextField has a very close relationship with the TextFieldCell. And that's because every control has a NSCell. That's just the built in relationship.
All controls have a cell. If we were to take a look at another control, first remember now that NSControl is an NSView. And most controls implement their drawRect method from NSView. We implement drawRect simply by forwarding over to the control cell, telling the cell, draw in frame in view, draw with frame in view.
And we pass the area of the control that needs to be drawn using the cell, and pass the control itself as the view that's being drawn into. And that's how the cell draws. So the cell doesn't have to worry itself about its place in the window. It's the view that is placed in the window. It's the view that has the hierarchy of, of subviews.
The cell is just about drawing. And if we took a look at another control, NSButton, it's the same story. We have an NSButton control and an NSButton cell, which is responsible for the drawing. And there wouldn't be much sense to this separation if it was always a 1 to 1 mapping. And it's not the case. It's not always 1 to 1.
We have other controls, like NSMatrix. So here NSButton using an NSButton cell to do its drawing, and NSMatrix using button cells to draw its content. And remember it can, an NSMatrix can use an arbitrary number of cells, and there's no one matrix cell, it's just whatever cell it's handed to draw. So it could be text, text field cells, button cells, image cells, anything that's a cell the matrix can draw and stamp out the contents of each cell area very cheaply.
So that's controls and cells, and their relationships. And if we step away from matrix for a moment and look at another control that uses multiple cells we come back to our old friend, NSOutlineView. So for NSOutlineView, which is a control, it implements its drawRect, basically, by drawing its bordered area and its background, and then forwarding on for each row of data that needs to be displayed to a data cell.
In this case here we have just simple text field cells. So knowing that, we know that we can get the rest of our work done showing those custom view, those custom rows with images, and text, and photo count information. All that all in one row, by using the OutlineView delegate, OutlineView table cell for table column, data cell for table column item.
Using this delegate method to hand back a custom cell that we'll interpose in this, in each row, to do the custom drawing. So just by using OutlineView Cell Delegation, we can get a lot farther in, in implementing our designs. I'm going to show a quick demo here of what we ended up with.
[ silence ]
[Ron Lue-Sang]
So just for reference, here we have the original mock up, the original prototype that Joar had shown off.
[ silence ]
[Ron Lue-Sang]
And with a bit of work and some custom controls this is where we end up, something that looks just like our user interface design pattern, our, our user interface designs. We can select our projects, and they flip between the photos that are on, in those projects. We have our canvas over here that we've gone ahead and implemented, so we can add photos to them.
Click around and drag. You'll notice that we also have our split view set up so that we, we're using delegation here to prevent the source list from getting too small. We also have live previews in our source list. So using our custom cell drawing, we redraw the, the, the cell of the project that's been updated, just by dragging around the photo.
[ silence ]
[Ron Lue-Sang]
[ silence ]
[Ron Lue-Sang]
So I don't have enough time, unfortunately, to go into all of the details in the project itself, I just wanted to show you that with a little bit of code we can get pretty far using custom controls to match our design. So we're most of the way through our little journey. We've touched on the basics of Cocoa drawing, and the basic, the fundamentals of how to draw using Cocoa API's.
Looked at an approach of how to create custom controls that will fit with the design that we've been, that we've been using. Now I want to touch on adding custom behaviors to our application to really bring it to life, including adding animation as feedback for our users.
[ silence ]
[Ron Lue-Sang]
When I talk about behaviors I'm really talking about responding to user events, responding to what the user is doing. And after talking about some of event-based API's, I want to talk a little bit about supporting drag and drop, which I think is an important thing for every Mac application.
I don't know if you were able to see it during the demo. If you saw when I moved the mouse over our individual photos, the icon for our cursor changes as it enters and exits the photo area. The way that works is, we have a view hierarchy in that canvas.
And each photo has its own, is in its own view. And those views, the view class that we used to hold the photos implements the methods, mouseEntered and mouseExited to adjust the, the, to show this little info icon at the bottom right, as well as to switch the cursor for the, the icon for the cursor.
This is a good pair of methods to know about. It's really easy to add some event-based feedback for the user just by keying off of these two callbacks, but you have to ask for these events to be delivered to your view. And the way you ask for them is usually by implementing update tracking areas in your views.
Update tracking areas gets called whenever your views, for example, when the view's bounds change. The area of the view is no longer the same as it was before. The area that's interesting for the user is now smaller in this case. Or if the contents, for example, we, we wanted to show this little info button.
If the area that that info button is supposed to show up in changes, we're given the opportunity to update where the interesting rectangles are. And the way we do that, inside of our implementation of update tracking areas is just to remove the old tracking areas that we were watching before, the old frames, and add new frames that we want to watch. And we can say which, which events we want to hear about for those frames.
So when we go to create new tracking areas to add, using add, add up, add tracking area, we make sure to add this option during creating the tracking area, NSTracking, mouseEntered and exited. By creating tracking areas with this option and adding them, then our view will get the mouseEntered and mouseExited callbacks that we were talking about, so we can add that custom behavior, like showing extra adornments to our view when the mouse enters or exits.
[ silence ]
[Ron Lue-Sang]
There are a lot of other events that the view can respond to. These are all callbacks that we inherit from NSResponder. Things like mouseDown, mouseUp, events that tell us which of the mouse buttons was depressed, mouseDragged events during the click and drag of the mouse. I want to talk about two of these specifically, mouseDown and mouseDragged.
[ silence ]
[Ron Lue-Sang]
MouseDown events are pretty important. You can imagine that we implemented our canvas with the individual photo views by implementing mouseDown inside of our canvas or the photo views, right? Whenever the user clicks within a photo view, we want to be able to, to show that information about the photo size underneath.
[ silence ]
[Ron Lue-Sang]
And the same thing for the little info window. Hypothetically when the user clicks just in that little info I badge, we want to be able to show some info about the photo. Now the mouseDown method gets past an event. And this is a user event. This is an NS event that represents what happened, what the user did at that moment that melts down the path, got invoked on our view. And you can find out things like, was it a mouse event, was it a key event. And for mouse events you can ask for where was the mouse at the time the event happened? And you get the location of the mouse in the entire window.
That's great, but usually you're interested in figuring out where the mouse was in the view in the window. You want it in view, in the views coordinate space. And there's a simple way of getting that information as well, taking the base location in the window and using the method convertPoint:fromView:inPathNil. And then you can convert the location from the window into the views coordinate system and get an idea of where these are clicked, whether they clicked on the little I badge or just on the, the general photo itself.
[ silence ]
[Ron Lue-Sang]
Now this is a really common method to implement, if for no other reason than to keep track of what was under the mouse at the time that the click happened, so that then you can go and implement this method, mouseDragged. Pretty common thing is to start off mouseDragged by looking at, well, what was under the mouse at the time that mouseDown happened. This gives you a good clue of, in our example, in our application, figuring out which photo the user is trying to drag.
[ silence ]
[Ron Lue-Sang]
From there I want to talk briefly about drag and drop. You saw that I could drag photos from the desktop into our canvas to add them to our project. Don't be fooled though. Just because I was talking about mouseDragged events, handling mouseDragged events, that doesn't mean that we've implemented drag and drop. Those mouse events are about the location of the mouse within the window.
Those, those are callbacks about the mouse moving around within your view. Drag and drop is a data exchange operation, from one view to another, or from a window to another view, from another application to your view. It's data exchange in the same way that cut, copy, and paste from another application to your app is a data exchange operation. In order to support that in our application we simply use a pair of methods. Our canvas is registered as a drop target.
We do this by calling registerForDraggedTypes in our canvas. It registers itself as accepting certain data types for drag and drop operations. This sort of thing is usually called really early, like in awake from Nib, or from init, inside of our canvas. Definitely before the user has any opportunity to start dragging something into the view.
Once the view is registered for these dragged types, there will be some conversation between the view and the drag and drop machinery, asking is it OK to drop this piece of data? Is it OK to put it here? There's a certain dialog that goes on, and it culminates in asking our, our view to perform this drag operation.
And that's really where the view is meant to pull the data from the drag and drop machinery and add it to the view. PerformDragOperation, passes a dragging info object that has info like, what was the source of this drag? Where was this coming from? Information like where was the mouse at the time that the drag operation concluded? And the draggingPasteboard, which will be where the data for the drag and drop operation current, was put by the source. Again, this is like cut, copy, and paste, where the contents of cut or copy gets put onto a pasteboard. And now our receiving view is able to pull that data off of the pasteboard.
[ silence ]
[Ron Lue-Sang]
So those are a few important topics, event handling in general, and supporting drag and drop as a, as a concept. I want to talk now about adding animations for our application. And for an application like this, especially something that handles photos, well you can see here it'd be pretty easy to, as a user, drag in a ton of photos and then end up with just a big jumble. It'd be nice if we could clean this up for the user. So we could do that by taking all the views and moving them into some cleaned up place.
That would work, but it would be nice if we could do this with a nice transition, a nice animation, to give the user some context, so that they know what's going on. They can see what's happening better. Animation is just a great way of giving the user the feeling that you care.
Now before I talk about exactly how we go ahead and implement animations in our application, I want to cover one thing, how we draw with views normally. When I say normally I'm talking about all of the, the drawing topics that we've covered so far in the talk. This is the way that views draw.
They have one shared buffer that's owned by the window that they're in. And all of the views live in a stack; they're ordered in Z-order close, the closer to the user, the higher the, the Z-order number. And the bottommost views draw first. And they just draw one after another, drawing on top of each other into the same shared buffer. So they overwrite each other's pixels.
[ silence ]
[Ron Lue-Sang]
This is bad, because, well, if you were to reorder any of these views you have to go and redraw the overlapping areas so that you get the new overlapping contents. Order matters in the drawing. So reordering means you have to redraw the overlapping areas. And the same is true if you were to change the contents of any of these views.
You'd have to go back and redraw the overlapping area for that view. Now remember that views can be in a view hierarchy. They can have a list of subviews. And the drawRect method for any of those views could have an arbitrary, have arbitrary performance characteristics. We don't know how long every view takes to draw. Ideally, they all draw very quickly. But even at that it's expensive to have to redraw all of the overlapping areas, all of the overlapping views here every time one were to change its content or position or ordering.
Well with Mac OS X Leopard, with 10.5, we got a technology called Core Animation, which allowed us to add layer backed views. Layer backed views are just NS views that have had their set once layer properties set to yes. That's all a layer backed view is. Core Animation and Cocoa has been able to add layer backed views, NS views that have set one layer set to yes. The difference this makes is, instead of drawing into one shared window buffer now, all of our views get their own little layer backed view, layer backed buffer to draw into.
This is powerful, because once a view has drawn into their layer backed buffer storage area, if their contents don't change, then they don't need to redraw. Draw once and that's it. This is great for static images, static content. You just draw once into this buffer, and all of the positioning and layering that happens later is performed by the very fast GPU.
[ silence ]
[Ron Lue-Sang]
Again, this is all made possible with Core Animation. Along with Core Animation we got a few classes, CA Layer, which is the layer in layer backed views. And Core Animation, CA Animations, these are objects that represent the, the animations that we want to use in our applications. They're, they're containers for specifications of how to animate our views. And something that's subtle, but is still good to know about, is the protocol, NS Animateable Property Container.
This is a protocol for all objects that have an animator proxy, and NSView happens to be one of those objects. So what we can do with a layer backed view, instead of telling a view to move by setting its frame, frame origin, or to resize by setting its frame bounds, its bounds, we can tell the view animator to do the same thing.
We access the view animator as, as we have here just by doing view animator, and pretend that the animator is the view itself. For all intents and purposes, as far as resizing, rotating, repositioning, setting the opacity, we can treat the view animator as if it's the view itself.
This is great, because all of our NSViews, all of our NSControls, our custom controls, our basic Cocoa controls, everything that's in NSView has an animator. So now repositioning them, fading them in and out, resizing them, we can animate all those properties just by messaging the view animator. And we can put them together, changing the frame, changing the opacity at the same time. And it's really hard to talk about this sort of stuff. I'd like to jump to the demo. Let's see, go close the older model of this app.
Here's, again, this is the same version that I demoed just a bit ago. I wanted to show one thing, so if you didn't notice the little I before, there it is. If we were to get info for the object, for this photo, we have this nice flip animation. The cool done button that Joar had drawn earlier, we have those as well.
So one thing we've also added the clean up action, so we can get this nice motion effect. All we did is calculate where we wanted to move the photos to, and instead of doing view, set frame, we do view animator set frame. And they animate there to their locations and sizes.
One thing you might notice if we were to switch between projects, you guys see that? There's a subtle transition. They don't just pop in and out, there's a very subtle like short fade in and out when we switched projects. Now that's, that's an animation that's happening for us automatically. We, the timing for it is automatic; we don't have to set any parameters or anything.
We get this subtle animation for free just by telling a view animator remove from superview, and then adding new views by doing view animator, add subview. We, we don't have to change anything else in our code to get this nice subtle animation. I do want to add one thing though, I...
[ silence ]
[Ron Lue-Sang]
[ silence ]
[Ron Lue-Sang]
So here's the method that currently switches between the old photos and the new photos when we switch projects. We're told to switch to showing these photo items. Right now all it does is this, and it's a basic swap out the old ones, pop in the new views, that's it. I'm going to delete this right now, and swap in this implementation I have precanned.
To walk you through it this is the, a CA Animation. This is a subclass of CA Animation, CA Basic Animation. What it defines is a property; it's associated with a property of an object that's changing. So in our case the frame origin or its frame size. And we can set the duration that we want for the animation if we don't, don't want to use the default animation settings.
We can adjust the timing function, there are a couple of prebuilt ones, ease in, ease out, linear, ease in and out. We can even substitute our own fancy one with more stop points. And we can set ourselves as the delegates of an animation. So that when the animation is done we can go back and do some clean up. Now we do some work here figuring out where the old views are, and we do, we make a little, we do a little trick here where we add an overlay view where the photos are currently, and pop those photos into the overlay.
And that's invisible. So we have an old overlay for all the old photos. And we create a new overlay for all the new photos, and position that overlay off screen. So now we have two views holding a bunch of photos, one view for the old photos and one view for the new photos. With this simple trick after setting up the overlays we can go ahead and set the animation for both overlays to be the same.
And the animation is simply a transition moving downwards or upwards. It's a frame move. And when we, when we change the frame for both of the overlays we make sure to message the overlays animator. And that's the only difference. Instead of setting the frame on the overlay themselves we, we change the frame for the animator. And what our delegate callback gets is the animation itself that just finished. So when the animation is done our delegate, which is our cells in this case, will be told that the animation is done. And we can pick information out of the animation itself.
We can, we can use it as a generic key value coding bucket, so we can put information into it at the time that we create the animation. So that when the animation is done our delegate callback knows, ah, these are the two overlay views that I have to fix up, and throw away the old one, and pop the new photo item out of the new overlay and into the canvas. It's a pretty simple pattern. And it's just easier to show you what, what you get from this. So for those maybe like 20 lines of code we get this nice up and down transition that gives you the, the impression that you're on an infinite canvas.
Right? Still adding photos, we still get our live previews. And, actually one neat thing we can do, if I hold down the Shift key we can slip these and we can still switch as it's animating. It's actually kind of fun to do. I forgot to hold down the Shift key. So just an example of some of the things that you can do with CA Animation and Layer Backed Views.
Oh, one other thing I forgot to mention, now with Layer Backed Views, the whole point of not having to draw into that, that buffer every time some content changes or the position changes, that means that we can draw really fast. When we don't have to redraw all of the overlapping views, we can draw really fast.
That's one of the great advantages of Core Animation and Layer Backed Views. And to, to show you that I have a simple application that has two image views overlapping each other, one that's a big tic tac toe board, and one that's a simple tic tac toe little piece.
And all I do is I move the frame of the tic tac toe piece up and down. Now you can still message the animator of these views without turning on layer backing for those views. It's important to note, you don't have to have the views set to wants layer yes, but it certainly helps. I'll show you why. So if we, you'll notice that there's this yellow flashing whenever I kind of switch between apps here.
Well that's because in our project I've turned on this argument, added this argument, NS show all drawing, yes. Feel free to write this one down. This is a great debugging tool when you're implementing custom drawings to make sure that you're not being called to draw your views contents too often. This is great even if you're not implementing your own custom controls, just using some standard controls, making sure you're not dirtying areas of the view too often.
So with that on we see this flashing every time our views have to redraw. Now I'm going to go ahead and hit the go button, and you'll see the animation of our piece. So that's a lot of drawing just for moving the view 120 pixels, right? Now if we turn on wants layer for all of these, we turned it on for our background image, as well as the foreground image view with the X piece in it.
We hit reverse; it's a big difference. This is what a layer backed view is supposed to, performed like. So this is a reason for you to consider using layer backed views in your custom controls in your applications. With that, let's go back to slides really briefly. For more information please contact Developer Tools Evangelist Michael Jurewitz. Hey Michael! Lots of documentation for all the stuff we've touched on so far for the Cocoa drawing, the basics of drawing with Cocoa API's. The View Programming Guide is a great source of information. The Human Interface Guidelines, which we talked about earlier.