Application • 1:12:06
In this session, you’ll discover strategies, shortcuts, and practical tips in Cocoa that can help you quickly solve a problem or add unique effects to your application. Learn about custom controls, advanced graphics techniques, cunning tricks, and more from the Cocoa experts at Apple. This is an intermediate-level session.
Speakers: Chuck Pisula, Tina Huang, Hansen Hsu, Doug Davidson
Unlisted on Apple Developer site
Transcript
This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.
Good afternoon, everybody. Welcome to session 411, Cocoa Tips and Tricks. So let's just jump right in, if I can get this to work. There you go. Okay, so today we're going to be showing you, well, basically we're going to have a bunch of engineers from the Cocoa Frameworks team up on stage answering the sort of questions you might have and showing you how to put some cool features and some useful things into your applications. We're going to show lots of source and lots of demos. And just like everything else at the conference, it's going to be available for download on the ADC website, all of the code that you see today.
Last year, we showed you lots of cool features, lots of useful things. I hope we had a lot of fun. This year, we��ve got even more things to show you. You can see there��s a lot, so let��s just jump right in. My name is Chuck Pisula. I��m an engineer on the Cocoa Frameworks team. Let��s take a look at the first question.
I have a table view, and it doesn��t know how to sort, though. I��d like to add sorting. Can you tell me how hard that is? Well, it turns out it��s really pretty easy to do. And what we need to do is two things. We need to define which table columns are sortable, how those table columns are sortable, and then we need to provide the code that actually does the sorting.
But NS Table View is going to take care of everything else for us. It��s going to handle the UI, handle the clicks, and in fact, it��s going to tell us when we need to sort. Now, if you want to be even lazier and do even less glue code and less work, you could just switch to bindings, and this would all just work. Thank you.
Okay, so how do we do our first job, which is defining the sortability of table columns? Well, what we need to do is provide a sort descriptor prototype for each column that we want to be sortable. And in the sort descriptor prototype, we��re going to provide three bits of information.
We��re going to say what the key is that we��re sorting on, what the initial sorting direction is, and what the selector is to use when comparing two objects during the sort. Now, like many other things, we can also set this in Interface Builder by inspecting the table column and setting the three attributes.
Okay, so our second job is to actually do the sorting. As I said, TableView is kind enough to tell us when we need to update our sort ordering. It��s going to send us the TableView:sort descriptors did change: and the second parameter is going to be the old list of sort descriptors in case we know that we can do something more efficient by being given the old sort descriptors. If we want the current sort descriptors, we just turn around to TableView and ask it for the sort descriptors. Now, for some reason, if you want to manage all the sorting yourself, TableView provides a routine to set the current list of sort descriptors.
And another interesting note is that the sort descriptors are persisted along with other information about table columns that are persisted. So across running of your application, TableView will remember what the last sort ordering was that the user was Okay, so now that we know when to sort, we need to do the sorting.
And we��re going to use some conveniences provided by NSArray. NSArray knows how to take an array of sort descriptors, which again we get from the table view, and knows how to sort using the information in the sort descriptors. One of the things is the key, which is going to do the sorting on. And finally, you��ll see at the bottom an example of how we might sort an array of things using a sort descriptor. We��re going to sort things in this array based on the first name value of each object.
Okay, before we go into this demo, I wanted to do just a quick overview of what we��re going to be looking at, since what we��re really going to be doing is extending an example that exists on the system already. And that example lives in Developer Examples app kit.
It��s called drag-and-drop outline view. And what it does is display some hierarchical data. And the data that it��s managing is sort of a simple tree structure with nodes, and each node has a pointer to some data. So at this point, I believe I want to go to the demos.
Okay, let��s take a look at the application running and see what we��re going to get in the end. Okay, what you'd expect. I click on a table column, it sorts. This actually, in the existing demo, it already knows how to sort based on the name column. And in a second, we'll see that it really was a hack, and we're going to do it a better way, and we're going to do it in a completely generic way. Now, we're able to sort on each column.
Okay. So, let's first take a look at our first job, which is to set the sortability of the table columns. We did this in Interface Builder. And the first column is going to sort each object based on its name key. And it's going to use compare colon to do its comparison.
And it's going to sort its initial direction as ascending. And we could have actually left compare colon off because that's the default if you specify null or nothing. And then we'll see that we specified some values for the other columns. The key will be is expandable for the second column and is group for the last column.
Okay, for this example, I actually�� I'm going to show you the code a little bit of a different way than you've probably seen in all of the sessions. I'm actually going to bring up File Merge since, as I said, this is a demo on the system. What I want to show you is what we've added to it. So it lives in�� the original lives in Developer, Examples, AppKit drag and drop outline view. And we��re going to compare it to this set of code.
Okay, and let's see what we've added. Well, we've changed the application controller, which is the data source of the outline view, and there's one difference. We've added the sort descriptors did change colon delegate method. And what we're going to do is tell our data to sort, our tree data. We're going to add a sort using descriptors method. And then since we probably changed the order of things, we're going to tell the TailView to reload the data and display it in its new order.
So let's take a look at simple tree node next, which is a subclass of tree node. And really all I want to show you here is that we've removed our hack from before. We used to sort things by forcing it to compare using the name.
[Transcript missing]
So first we go and we tell our node children, which is an array, we tell it to sort itself, to reorder itself based on the sort descriptors. And then we recurse.
And finally, since we want to sort on keys in the data, and we have a pointer to our data, but we don't actually respond to all the methods in the data, we're going to do a little trick, and we're going to override value for undefined key. So when we're trying to compare based on a key in the node which it doesn't respond to, like the nodes don't respond to name, it's going to go off to the data object and ask it for the value for name. And so by doing this, we're going to be able to sort based on keys in the data object. Thank you. and using those small set of changes, not many changes there, we��re able to get sorting in our table view or our outline view in this case.
Okay, so if we could go back to slides, Okay, well, that's great, but, you know, your parents out there are just going, "Sorting's not boring. I'm never going to sort anything. It's no fun." Can we make it a little more fun? So, could I add something like animation to the sorting so it's a little more whizzy, a little more fun to do, a little bit more, you know, Mac OS X, kind of sliding around, fun style? And, certainly, we can do that.
And what we're going to do is make a subclass event a style line view that knows how to do all of this. And what it's going to need to do is, first, take a snapshot of the location of all the items in the outline view before you do the reload. And that's so that we can map index items from their old location to their new location. So, during the animation, we're going to slide things from the old place to the new place.
So, before we load, we make a snapshot. And after the data is sorted by the data source, what we're going to do is reload in a way such that we create an index mapping, which shows that we have a snapshot of the items in the outline view. And after that, we're going to get an index mapping, which shows where things went from and to. And then we're going to do some custom drawing because, well, we're not drawing things at fixed locations. And, finally, we're going to use NSAnimation to help us manage our timer and get the nice smooth curve values so we get a nice, you know, fun, smooth animation.
Okay. So as I said, we're going to make a subclass, it's going to be called animating outline view. And first we're gonna add a method to it called. Well, I like long method names. For those of you who know me, it's called prepare for reload movement animation. And this method is supposed to be called by the data source before it reorders the data. And this is the chance for the animating outline view to take a snapshot to take the snapshot location where things currently live. Then after it sorts the data, it's going to call a new method that is on the bottom called reload data with movement animation.
And this is basically a new kind of reload that we're going to make, which knows how to make the index mapping, start up a timer, which fires and is going to control and run a reload. on our animation. And of course, it's also going to reload the data.
Okay, as I said, we don't draw rows at fixed locations anymore, and we're going to override, draw a row in ClipRect, and what we're going to do is, over time, smoothly animate the rows at new locations, and we're actually going to use a nice little trick by drawing these rows.
Instead of into the table view, we're going to draw the rows into an image, and then we're going to blit the images onto screen. Because we're going to be doing this lots and lots and lots of times very rapidly, this means we only have to draw the row one time, and after that, we can always use these images that we've cached away, and we can get better performance.
Okay, so we're not drawing rows into a table view. We're going to do a little trick, and we're going to draw rows into an image. So to do this, we're going to create an image, we're going to lock focus on it, and then we're going to tell table view, go ahead and draw rows like you normally did. But, say, row three, where does it draw? It probably draws at a coordinate like y of 51. And our image, we don't want to make every single image for every single row as big as the table.
We just want to make it the height of a row. So what we're going to do is trick the table view into drawing everything during animation at a y coordinate of zero. And by doing this, we're going to be able to draw into the image. And later on, we're going to use it, we're just going to blitz screen over and over and over. Okay, so if we could go to the demo machine, please.
Now, just so we see what we're going to get, let's take a look at this one again before we start. And, well, we've got sort, it's not very fun yet, but let's click the fun button. And let's make some more data so it's a little more interesting. And we've got some nice fun little animation going on. All right, so let's take a look at the code for this. First thing I want to take a look at is the Not That class.
is the Animating Outline View, our new class. It��s a subclass of NSOutlineView. It has some instance variables that help it manage the animation. Now, this example is a little more complex, has a little bit more code than some of the other demos, so I��m going to kind of breeze through it because you��re going to get to look at it on your own. And for the most part, this is all some little�� lots of little details that need to be taken care of. It��s nothing too difficult. and we've added an interface that our data source gets to use. Okay, so let's take a look at the actual code.
and jump down to prepare for reload animation. And only if animation is enabled, we��re going to do a couple things. First, if we��re animating before, we happen to have some of the old stuff laying around, we��re going to throw it out and start over. and what we're going to do is�� sorry, I guess I want to jump down here�� we're going to create a dictionary, which we're going to keep as an instance variable. It's something that's going to be used by the next reload. And it's going to be the snapshot of the current order of all the items. Now, if you have a very, very, very large outline view, you can imagine this performance might not be great.
So I'm going to leave it as an exercise up to you guys to figure out a more efficient way to do that kind of a trick. And we marked some other information, which is the current row selection, so we can fix it up later. Okay, so jumping ahead.
We have our reload data with movement animation. And as I said, it��s a new kind of reload data. It��s going to do reload data and a couple of other things. The first thing we want to do is computer index mapping. I��ll let you guys take a look at how that��s done offline. If we��re currently animating, we finish the current animation. We just bail and start over.
And then once we have an index mapping, first we want to fix up the selections because things have moved to new locations, so we're going to make sure the selection matches that. We're done with the data that we marked before, so let's free it, clear it out. And finally, jump ahead a little bit.
[Transcript missing]
We're going to use non-blocking mode. We're going to create a place that we can store the images that we're going to use to draw during the animation. And then we start the animation.
and it turns out that our subclass of NSAnimation is going to tell us whenever we need to update. And it's going to tell us by sending us this message: "Progress animation, current progress did change." And when we get this, well, we're either at a point where we're done with the animation, so we'll finish it, or we'll force a re-display.
And when we re-display, I'm going to jump ahead a little, we eventually end up getting into draw a row. And again, what draw a row is going to do is draw using an image. So first thing it's going to do is figure out, well, where do I need to draw? It's going to get the nice smooth curve values, do a little bit of math in this routine that I have, and it's going to figure out where the row should be at this point.
Then we're going to find out, well, if it's not actually on screen, let's just bail. We don't want to draw. If we haven't generated an image cache yet, let's generate an image by the code I showed you in the slides. We're going to create an image, draw into it, and then cache it away. And finally, we're going to just split the image onto the screen.
And again, here you see in Rect of Row, here's our trick to get the drawing to draw at a y origin of zero. and the rest of the code I'm going to let you guys take a look at online. These are just our helper teams to compute the index mapping, figure out where we want to draw things, and so on. Now let��s take a quick look at our animation subclass.
[Transcript missing]
This message whenever the animation updates. If we take a look at the code for this, it��s really simple. We have a cover for a net, stash away the delegate, and basically all it does is whenever the progress changes, it tells the delegate that it needs to update the animation, and that��s it.
And it��s nice because this class takes care of all the little dirty details of managing a timer, creating it, getting it to fire, and it hides the details of how it computes the ease in, ease out values, and things like that. All right, and again, once we have all that, we��ve got our nice smooth animation.
You'll see when you look at the code, there's some other neat little tricks in there. We try and make sure that we never�� If the table had 10,000 things, you don't want to animate something from 10,000 rows away. You sort of want to clamp it to two times the visible height so that it's never too far away from visible. Because if you animate from really far away, it wouldn't be on screen very long during its animation. So there's some neat little tricks in there like that that I'll let you guys take a look at offline. All right, if we go back to the slides, please.
Okay, well, we've got some really cool, fun animations, but, you know, I'm sure this has never happened to anyone out there, but it's the last minute, we're about ready to ship, and HI's, they've changed their mind. They want something, they want it to be in a table view now, not in an outline view. So what are you going to do? Are you going to change everything to be a table view, and are you going to change the data source to use different data source APIs? Well, here's a little trick.
What we're going to do is make NSOutlineView display looking more like a table view. We're going to trick NSOutlineView to draw, like, a flat list. And one advantage to this is that you only have to deal with one delegate and data source, so you don't have to change anything in your nib. And for those of you who want a unique identifier, outline views require unique identifiers per item, per row. But, of course, for some people, that's a drawback. So those are things you'll have to think about if you want to try this trick.
Okay, so we're going to trick NSOutlineView, and what we're going to do is tell it, set your indentation level to something like zero. Don't draw with a big indent, because that's one difference between OutlineView and TableView. And if it turns out that for some reason you're drawing a flat list, but you have some hierarchical data, well, it would try and draw those turndown indicators.
And we don't want to draw those, so what we're going to do is override the WillDisplayOutlineCell delegate message. And when we get that, we're going to effectively force the outline cell to be hidden. And that's what you see down at the bottom. If we could go back to the demo machine, please.
I got a little overzealous and closed things behind myself. Bring it up again. Now, I'm actually not going to show the code for this because you just saw all of it on the slides other than some little helper things I've added. And you'll see if you compare this to what already exists on The system for drag-and-drop outline view, if you compare it, I've added the ability to change the data file that's being displayed. And what it's going to do, if it detects that there's no hierarchical data, it's going to switch to using a flat list type display, using the code that you saw on the slides.
And so all we do, I know data file one happens to be a flat list. I click that, and it looks more like a table view. And I didn't have to change anything in my nib, and I didn't have to add any more code than what you just saw on the slides. Okay, so if we go back to the slides, please.
Slides, please. Thank you. Okay, at this point, I'd like to invite up on stage another one of the Cocoa Frameworks engineers, Tina Huang. Tina? and Mrs. Ford. Thank you very much, Chuck. Once again, my name is Tina Huang and I work with Chuck on the App Kit. So have you ever noticed that applications such as Xcode provide easy access to commonly used functions such as opening a file or creating a new project? And they do this by having a menu in the dock. Well, how can you add a dock menu to your application? Well, the very easy way and basically no-code method is add an IB. To do this, just drag a menu out into your nib file and connect it to your application's doc menu outlet.
But there are times where in your doc menu you want the contents to vary dynamically depending on the current state of your application. And so to do this, you have to actually create the doc menu in code. And all you have to do is implement in your application's delegate the application doc menu method and have that return your doc menu. But there are times where in your doc menu you want the contents to vary dynamically depending on the current state of your application.
And so to do this, you have to actually create the doc menu in code. And all you have to do is implement in your application's delegate the application doc menu method and have that return your doc menu. So we're going to take a look at this first. Notice that when I�� well, first of all, I'd like to mention that DotView is another application that you can get in your application�� developer application examples, and if you went to the Cocoa introduction talk, I think Ali showed you basically how you can create DotView. But here we have in the dock menu red, blue, and green. So we basically are allowing you to change the color of the dot in the menu.
However, to illustrate a dynamic menu, it kind of seems silly to have red in your menu when your dot is currently red since it��s meaningless. So we��re going to change it so that if the dot is one of the current colors, then take it out of the menu. So say I select blue and now that��s out of the menu. But if I were to change the color to something like maroon, now I have all three options available. So to take a look at the code���� And real quickly, I��m going to change the indentation here.
So���� Looks a little bit better. Okay. So taking a look at the code, we��ve implemented the application doc menu method. Looks a little bit better. Okay. So taking a look at the code, we��ve implemented the application doc menu method. Once we have that, we're going to take a look at what the current color of the dot is. And basically, if it's red, take red out of the menu and the same thing goes for blue and green.
And then for each one of the colors that remains, we create an NS menu item out of that color and set a nil key equivalent. Add the item to the menu and at the end return the menu we've just created. And that's it. If I can go back to slides, please.
So that's great. We can now change color easily in our dot using the menu. But let's say in addition we want to have the ability to change the dot size as well. Now we could just add on top of red, blue, and green the small, medium, and large size controls, but then we're going to have a really cluttered dock menu.
So what we really want to do is have the ability so that when you hit the option key, that red, blue, and green switches to small, medium, and large. Well, again, you can create alternate menu items in IBE. Now, alternate menu items are not unique to doc menus. They apply to any NS menu you have, but new in Tiger is alternate menu items are also available for the doc.
So I'm sorry, back to the previous slide. The two general rules you have with alternate menu items is that the key equivalents have to be the same and the key modifiers have to be different. So looking at how we would set this up in IB, remember that you always want to work in pairs of menu items.
So in this case, red is going to have the alternate menu item small. And to do this, I have on my���� on your left should be the red, the inspector for the red menu item, and on your right should be the small menu item. We're going to first take a look at the key equivalents. And notice here that they are indeed the same, and having an empty string for a key equivalents counts.
Then we take a look at the key modifiers and notice here that they are not unique. So let��s say for the small we are going to have the option key checked. Once you do that, notice that the treat as an alternate menu item check box becomes enabled and all you have to do is check that. And now small is the alternate menu item for red.
And again, you can do all of this in code by setting the key equivalent and the key modifier mask using NS Menu Item APIs, as well as setting the alternate flag. And I have a quick demo for you here as well. So first, taking a look at the running application.
We have here red, blue, and green. When I hit the Option key, I have small, medium, and large. So when I hit it here, it turns green with the Option. Notice here that if there are only two colors available, we remove the middle size always, because it would be weird to have small and medium. So that's what we're trying to accomplish, looking at the code. And by the way, when you look at your sample disk image, all these are the same file. They��re just slightly reordered for demonstration purposes on stage.
So notice that in addition to creating the standard red, blue, and green colors, for the alternates, I have the available sizes, small, medium, and large. Right here I have the code that basically says if I'm only displaying two color options, remove that middle size. And here's the interesting part.
So for every color I add, and notice that once again the key equivalent is nil, I also create a menu item for the size with the same key equivalent. And then I set the key equivalent modifier mask to be something different than the color, and then I set the alternate flag. And that��s it. If I can go back to slides, please.
So that��s fun. But changing the color of the dot is pretty boring and, you know, as Chuck said, OS X is all about fun, whizzy animations. So let��s animate the dot color so that it fades into the new color. Well, again, new in Tiger is NSViewAnimation, which is the only current public subclass of NSAnimation.
To create an NSViewAnimation, it takes an array of dictionaries with various properties set. The important properties that we are going to focus on for this demonstration is the target key, which should be the view or the window that you are trying to animate, and the proper effect. And in this case, we are going to use the fade-in effect. Once you set up your view animation, all that's left to do is to run it. So this is a very easy way for you to create animations in your app. And I'll show you the demo of that.
So now, rather than just having the color change, when I pick a different color, it does one of those fun animations. And the code for this is remarkably simple. So we have the original dot view, which is the color that we're actually changing, but we want the color to actually have the original dot behind the fading in color. So what we're going to do to trick it to do that is create a temporary dot view that has the same color, origin, and radius as the actual dot view. And then we add it to our view hierarchy, positioned above the actual dot.
Next thing we do is create the view animation with the temporary dot right here as the target. And once again, we��re going to use the NSViewAnimationFadeIn effect for the effect key. We create a view animation with�� oops�� With just that dictionary in it. And then we're going to set the duration to one second just to make it last a little bit longer. Whereas Chuck used the non-blocking animation for the table view sorting, we are going to run it in blocking mode so that the actual dot's color doesn't change until the animation is complete. And then we call start animation.
Notice that by the time we reach this point here, since we are running in blocking mode, the animation is done and so it's okay for us to release the animation and remove the temporary dot from the super view. And that's it. If I can go back to slides.
This animation is fun, but what I really want is to be able to drag my view into a file in a place such as Finder. But when I start that drag, that location is unknown. So how do I create the file? Well, there are a few possible solutions. We could put the data onto the pasteboard as an NSPDF pasteboard type. However, that��s going to leave the responsibility to the destination to actually write that to a file.
Or we can create the file at some temporary location and once that drag is complete, move that to the drop location. Or ideally, we��re going to delay the file creation until the drop occurs. And to do this, we use something called HFS file promises. In NSView, we have a method that allows you to drag a promise file from Rect, etc. And basically, what you hand it here is an array of file types that you promise to create once a drop occurs.
Once the drop occurs, we get a call to name a promise file dropped at destination. Here, you are handed a URL containing the drop location and you lazily create the file in that spot. And to illustrate this, we have a call to name a promise file dropped at destination.
So let's say I want to be able to drag this and have a dot in a PDF available on my desktop. And notice now that I open in preview and look, I have my dot. So you can send Christmas cards to all your friends with lots of dots you create.
And so looking at the code here, in the mouse drag method, here��s where we promise the file type PDF. And then once the drop occurs, we implement the names of promised files dropped at destination. And again, the drop destination is handed to you as a URL. We have some clever code here that basically computes the file name so that if we don't overwrite a current existing file.
For those of you who have never seen this method, this is an NSView method that basically allows you to create a PDF out of any view. So we create the data with that. And notice here that I create the file URL and I root that file name to the drop destination. and a write to that file. What I return in this method is an array of the file names I have written.
And so that��s great. But let��s say I have this Finder window and I��ve traversed this whole hierarchy for where I want to place a file. But notice that as soon as I click to drag, I��ve now covered my Finder window and I no longer know where to drag to.
So what you really want The first step is to delay that window ordering so that once I start drag operation, the application is not ordered front. But if I click on the mouse up, the window orders front. So that way it's really easy if I have my finder window, I can drag and not block that finder window.
And if I go back to slides here. So how do I prevent my application from blocking other windows during a drag operation? And this is very simple. In NSWindow, there's an NSView method that you can override, should delay window ordering for event, and if you return yes, the event���� the window will not be ordered until your mouse-up event. And now at this point, I'd like to invite up the next presenter, Hansen Hsu.
Thank you, Tina. Hi, my name is Hansen Hsu, and I��m an engineer in the Cocoa Group. And for the next ten minutes, I��d like to show you how you can use child windows and Overlue windows in your applications. So let��s say I have an application and I have a main application window and I want to create a secondary window. Actually, go back to slides, please.
Well actually, I'll just show you if it's a... This is it... I have a main window here, and here's my auxiliary window. This is useful for, say, I have a utility window or a tool palette that I want to be stuck to my main application window. Such that if I move the main window, everything moves along with it. So, how do I do that? Back So we're going to do this by using something called a child window. We're going to make our auxiliary window a child window of our main window.
So we do this by, very simply by calling a method on NSWindow called, surprisingly, addChildWindow. Ordered. And we're going to give it one of two ordering modes, above or below. And so we can make our child window either ordered above or below the main window. And that's pretty much it. That's all we need to do. So now let's take a look at this in our actual code. Back to demo machine one, please.
So here we are. We��re going to start with the Dot View application again that we all know and love. And we have added a new controller class to our Dot View. and we're going to set up our child window in Awake from Nib. So we could have created our child window either in Interface Builder, but today we're going to do it in code, just to show you what it looks like. And here we're going to set up the frame rectangle of our child window, and then we're going to instantiate our child window here.
Next, we're going to tell the main window To add our child window, and we're going to order it above the main window. Now next, after that, we��re going to order it above the main window again. That seems a little redundant. The reason why we��re going to order it relative to the main window again is to work around a bug that I found when I was writing this demo involving I wasn��t receiving mouse clicks correctly. So what turns out is we need to make this call in order to tell the app kit to properly register itself for events. So without doing this, the window doesn��t���� the app kit doesn��t know that the window is actually there. So we��re going to do that.
Next, we��re going to draw a dot view in our child window. So we��re going to create a new dot view, and we��re going to set the content view of our child window to the dot view. So as you can see, going back to our����oops����we��re Going back to our�� I think I may have recompiled it.
Going back to our demo here, that we can move the dot around. and we can move the dot around the main window and I can move this child window anywhere I want. So I can move over here and everything just works correctly. It will always stay in the same relative position to the main window.
Great. So now I've got two dots, one in each window. So given that my brain sometimes thinks like a three-year-old, I want to connect the dots. Go back to slides, please. So, like any three-year-old who plays connect the dots, I'm probably going to draw outside the lines. In this case, I have to draw outside the lines because I'm going to be drawing a guideline in between the two windows, from the dot in my main window to the dot in my child window.
So I have to draw outside the bounds of my windows. How the heck do I do that? Well, the answer is you don't. I'm going to have to fake it. So I'm going to fake it by creating a new window, a third window, and I'm going to make that window invisible. And this window is called an overlay window.
Now, a number of things to remember about overlay windows. An overlay window, number one, and most importantly, is just another child window. So it��s exactly the same as my previous child window. Except for one thing. Well, several things, actually. Two, we��re going to overlay it above its parent window. And three, we��re going to make its background transparent. This is probably the most important thing.
We want it to both appear transparent, invisible to the user, and we want it to be transparent to events, so that if you click anywhere on the overlay window, mouse clicks pass through to whatever is below it. And lastly, we want to make the overlay window borderless. So that means we can��t create an IB. We have to do it in code. So let's go back to the demo and take a look.
[Transcript missing]
Everything over here is the same code as before, setting up our child window. Now we��re going to set up a new window called an overlay window. We��re going to call it an overlay window. Now, since an overlay window is just another child window, if you take a close look, The code is almost exactly the same as what we did when we set up our child window.
Some differences. One, we want to draw on top of both our previous two windows, so we want the size of our overlay window to be the union of the other two windows. Next, we're going to programmatically instantiate this. Note, we're going to give it a mask, an NSWindow borderless window mask. Then, we're going to add it as a child window on top of our main window.
Here, most importantly, these two lines were going to make the window background transparent. First, we��re going to set opaque no so that events will pass through the window background. Then, we��re going to set its background color to clear. Now we need to draw our guideline into some view in our overlay window. So what I��ve done is I��ve created a custom subclass of NSView that I��m going to draw into, and I��m not going to show that code to you now. You can see that on the disk.
But we��re going to instantiate that view, and we��re going to set it as the content view of our overlay window. So, if we run this, you can see, voila, we have a line drawn in between our two windows. And you can clearly see here that whatever is below it is clearly showing through. And I can move this around over here.
I can move the dot around, everything just updates correctly. So, I can go to the Another thing I can do is I can resize this window and everything will update. Now a couple of things need to be done in order for this to work. So one thing is if we move the child window We want to tell the overlay view to redraw itself. And if we resize the main window, we want it to do the same thing.
And another thing we need to do is we need to remember that since the overlay window's size is the union of these other two windows, since it's dependent on these other two windows, so if I move this around or if I resize either of these two windows, I'm going to need also to tell the overlay window to update its size. So we're going to do that in our controller again.
and we're going to implement two delegate methods on NSWindow. Window did move and window did resize. We're going to listen to these notifications and if the main window or the child window moves or resizes, we're going to do two things. We're going to listen to Tell the Overlay View to update itself, and we're going to tell the Overlay Window to resize itself.
Of course, there's one little extra detail is when we only want to do this if the child window moves, we don't want to do it if the parent window moves. And the reason for that is because when we move the parent window, the child window always moves along with it. So we'd be doing it twice.
So we don't want to do that. So here, we're going to check explicitly to see if the window that's being moved is the child window. So great. Everything just works. We can move this around. We can resize it at will. and we have our nice little guideline drawn between these two dots.
All right. Back to So there's a number of other things that we can do now that we know how to use overlay windows. For instance, we can draw controls in overlay windows. This allows us to do interesting things such as draw controls on top of NSMovieViews and NSOpenGLViews.
As you know, neither of these two views can normally���� you can't normally embed controls in them. So this allows you to make it look like you've got buttons sitting on top of your movies or on top of So that about wraps it up for child windows and overlay windows. Next, I would like to introduce Doug Davidson, our resident text expert.
Thank you, Hansen. Let��s go to the next question. So this question here is about text completion. Now, if you want the sort of completion behavior where the user is typing along your application and hits a key and is presented with a list of completions taken from the standard system dictionary, then you have to do absolutely nothing. That behavior is built into the text system.
But There is a reasonable chance that your application has a somewhat better idea than that of what the user might be trying to type. So in that case, you can customize this behavior. And how can you do that? Well, first and most obviously, if you have a subclass of NS TextView, then you have complete control over all aspects of the completion behavior.
If any of you were here last year in the text talk, I presented an example of this. Some of the methods you can use, you can replace the completion behavior entirely by implementing your own version of complete. Or you are allowed to control the range in text that the user is presumed to be completing by implementing range for user completion.
Or you can supply a custom list of completions by overriding completions for partial word range, et cetera. And you can provide custom behavior when the completion is inserted back into the text by overriding insert completion for partial word range, et cetera, et cetera. But that's not really what I want to talk about here today.
What I want to talk about today is what you can do if you're not a subclass of NS TextView, but just the delegate of an NS TextView. And really, you can do quite a lot there. First and most obviously, the delegate of the text view is given the opportunity to control and alter and change and completely control the list of completions that is presented to the user.
Also, the TextViews delegate is notified and given the opportunity to control any time the user changes the text. and the TextViews delegate is notified and given the opportunity to control whenever the user changes the selection in the text. And if you put these together, you can combine them to produce a number of interesting effects. And if we can go over to demo number two, I want to present a simple little example of that. So let's just run it.
This application is what I like to call my Macintosh writer's companion. When you're writing about the Macintosh, You're always saying Mac this and Mac that. So when you're writing in this application, let me make this a little bigger. Whenever you type Mac, after a brief pause, it helpfully and automatically offers to complete it to one of these standard forms: Macintosh, Mac OS, Mac OS X. Mac, wait, there it is. So, how did we do that? Let��s take a look at the code.
So the primary thing that we have here is a little timer. And it��s the timer that handles this brief pause. And when the timer fires, it calls this method doCompletion. And really all that does is to call complete on our text view.
[Transcript missing]
And then when that timer fires after the brief pause and complete is called, then the TextViews delegate gets called again to determine the list of completions to be presented. And so we implement the delegate method for that. And there we just notice, we check to see whether the string that we're being asked to complete is actually Mac. And if it is, then we offer to complete it with, in this case, just this fixed list of Macintosh, Mac OS, Mac OS X. and that's all there is to it. So let's go back to the slides.
And we can take a look at the next question. So the next question here has to do with mixing images and text in our cells. Now when we think about a text cell, normally what we think of is just plain text. But what may not be entirely obvious is that you have the full power of the Cocotext system available to you whenever you use it. So if you can display it in TextEdit, you can display it in a cell or using string drawing. And of course, one of the things that you can do in TextEdit is to drag in images and have them displayed in line in the text.
So that's fine in TextEdit, but how can you do that programmatically? Well, programmatically, images are a case of what we call attached files. And the way that attached files are represented in a distributed string is as a special character, the attachment character, which has on it a special attribute, the attachment attribute. And the value of that attachment attribute is an instance of the class nsTextAttachment. nsTextAttachment models the contents of the attached file.
To do that, it uses an NSFileWrapper. That's optional. You don't absolutely have to have that if you don't need it. And the other thing that it has to do is that it has to provide some visual representation to be displayed in line in the text. And to do this, naturally, it uses a cell, an nsTextAttachment cell.
and by default, NS Text Detachment will automatically generate an appropriate text detachment cell. But you don't have to let it do that. If you know what you want your visual representation to be, you can supply it. You can use a completely custom NS Text Detachment cell subclass. If you want to see an example of that, come to the Cocoa Text talk on Friday, session 437. I'll give an example of that. But what I'm going to talk about now is that you can use a standard text detachment cell and just set an arbitrary image on it to be displayed in line in the text.
And here are some of the relevant APIs. The cells and controls and so forth can have an attributed string value, not just a plain string value. You can create an attributed string directly from an attachment. That's a convenience method. So you don't have to deal with the attachment character and attribute and so forth.
You create an attached attachment with a file wrapper if you have one. You can query it for its attachment cell. You can set its attachment cell. And you can create this attachment cell with an image of your choosing, if you like. So if we can go back to demo two, let me give a little example of that. So let��s run it.
Now what we have here Very simple. It��s nothing more than a list of colors. Each color has a name and a little color swatch to show you what it is. and perfectly ordinary except that what we have over here is just a stock one-column table view. No custom cell, no custom anything. It��s dragged directly out of Interface Builder. The only code that��s written here is in the data source. So let��s take a look at that code.
I made this really simple. All I have to provide the data is one array of colors and a parallel array of color names that I set up at the beginning, just fixed and static. And so the number of rows in TableView is always fixed as the count of those arrays. The really interesting code here is all in the TableView object value column row.
So what I'm going to do here is create an appropriate attributed string that has in it both an image and the text. So I get my color and the name of the color, and I'm going to create an attachment. I don't need a file wrapper here, so I'll just use nil. And I'll create a text attachment cell.
A blank one. And I'm going to create an image that I will use to be an image for my color swatch. And then I'm going to create a mutable attributed string. Now, when I create it to start off with, I'll just have the color name string as its contents. That's not very interesting. Then what I want to do is to take my text attachment and to insert it into the beginning of that string, that attributed string. So I'll replace the beginning of that attributed string with an attributed string created from my attachment. Very simple.
And then�� oh, yes. Somebody was actually asking about this on one of our mailings just today. I wanted to shift the image down a little bit. So I'm going to go ahead and do that. And then I'm going to go ahead and add a new Simple little shift, moves it down so they line up nice and neatly. Now I need to make this image, so I lock focus on my image, set my color, fill it up, and I get a color swatch.
Then all I have to do is attach the image to the cell, assign the cell to the attachment, Now everything is held onto by the attributed string, so I can release the image, the cell, and the attachment, return my attributed string. And we are done. That is it. So that's our example. We can go back to the slides. and you can download this tonight, use it tomorrow. And now, I would like to invite Chuck back up to wrap things up here.
Hi again, everybody. So I hope you have enjoyed everything so far, but we saved the best, most exciting thing for last. Hopefully we just blow you out of the water with this. We're going to show you some debugging tips. Well, it��s not very exciting maybe, but it��s something that everybody here has to deal with. And our goal with this here is to show you some tips specific to Cocoa which can help you in your daily work.
Okay, so what are we going to talk about for debugging-wise? We're going to show you some debug settings that you can set up that will help you, some GDB tips, and we've got some ideas for helping you debug crashes. All right, so first, what do I mean by debug defaults? Most of you probably know, but I'm just going to review. You can set defaults that apply either specifically to an application or to the entire system.
And I have an example here which shows how to set the NSFU default to some value. And I've done it in the command line using defaults right. So this is a way to persistently set a default that will live forever until you get rid of it. Or if you want to just set it for a particular run of the application, there's two ways you can do it. You can launch the application from the command line and specify -nsfu default and give the value. Or you could do it in Xcode by finding the appropriate pane and setting the value.
Okay, so what are some interesting defaults that might help you during debugging? Now, the first thing I want to mention with all of these things, these defaults, these different tips we have, maybe some methods we��re going to suggest, these are all meant strictly for debugging. They��re not strictly supported. They may be documented somewhere, maybe, but the idea is that these are to help you for debugging only and help to save you some time, but they should never be in a shipping application.
Okay, so NSShowAllViews. With that said, sorry, NSShowAllViews can be set to yes or no, and when you set it to yes, it��s going to show you something really ugly, but the really ugly thing might actually end up being pretty useful for you. What it��s going to do is show you where everything in the view hierarchy lives.
It��s going to outline everything with a different color, and it might help you to discover that, oh, I��ve got a blank view covering my other view, and that��s why it��s not�� why I don��t see my view, or you know, they��re not lining up right because other views are lining up. It��s a useful thing, I find. The next thing I want to talk about is NSShowAllDrawing. When you set that to yes, it will sort of act like Quartz debug.
Now, one�� those of you who use Quartz debug, one disadvantage to Quartz debug, it flashes the screen in yellow, but it flashes the buffer area that��s being flushed to the screen, and the buffer area is normally a coalesced area. So if I just drew an area up here and I drew an area down here, I��m going to�� the whole area is going to get flushed.
And it might mistakenly convince you that you��re drawing that entire region when you��re really not. So this is a little more targeted and will help you see what draw rect code is actually being invoked and run. One other note is that this works much better on Tiger. It doesn��t work quite as well on Panther, so that��s just using it mostly on Tiger.
The value can be set to yes, or it can be set to an integer which specifies how long to delay between the flashing of yellow. So in this example, in the open, I��m going to go to the open panel here. Let��s say I click on, I guess, media.
And when I click on media, it��s going to show me that the pop-up has to draw because it now has to show media instead of what it used to. And the new column that��s being loaded is going to flash. And that��s where you are going to see it. It��s going to flash, delay, flash. It��s going to be really slow, but it��s going to help you see exactly what��s being drawn. And it might help you discover that you��re over-dirtying areas and drawing things that don��t need to be drawn.
Two other interesting defaults I want to mention: NSLessCoalescedViewDrawing. I know it's a long name, but I didn't put it in there. What this will do is revert the coalescing of dirty recs back to, I guess, pre-Panther behavior. And pre-Panther, what happened was, if you dirtied an area up here and you dirtied an area down here, Cocoa just happily coalesced them all together and told everything in that region to draw.
And in Panther, a lot of hard work went into making sure that we didn't do that, and now we don't coalesce those recs together. But because of that, you may have been depending on that behavior, and things that used to get drawn just because of coalescing may not be getting drawn.
So this might help you discover why your thing's not drawing if you're depending on that old behavior. And then finally, just a quick note about NSTraceEvents at the bottom. If you're not receiving mouse events or keyboard events like you expect, you can turn on TraceEvents and see what the Cocoa event processing system is doing. doing.
Okay, so how about some GDB tips? So one thing we can do is we can PO an object. Now, we��re not making the object mad. What we��re doing is, at shorthand for print object. I��m pretty sure. Print object, and what it��s going to do is print to the screen what it gets back by invoking the description method on your object. Now, one thing that��s����I don��t know if this is actually declared in a public header, and again, this is strictly for debugging. There��s a method called debug description.
I forgot to put it on the slides, but it��s lower case debug, upper case description. You could override and provide a custom debug-only description if you wanted to have PO print something different than it would normally print when you��re doing, like, NS log of something. Okay? Some other interesting things are hidden parameters to methods. This can be useful to know about. There��s����every method has a self parameter, which is not explicitly declared it��s hidden, but it��s there.
And there��s an underscore CMD parameter, which is the selector. It��s the name of the method. And really the interesting thing that this����one of the interesting things about this is that if I��m doing an NS log, I get tired of doing NS log, type out the whole method name.
I can just do NS log, type out the whole method name, and I can say, you know, you see at the next step there, NSString from selector. So if I just want to print when I��m in a method, I can just copy and paste lots of code and I don��t have to keep typing the method name.
We��ll find out in a second. It��s also interesting other places. So if you wanted to print out the selector without doing PO, you could actually print char star of that. You find out it��s actually, I think it��s null terminated, but Okay, so some of these things become interesting when debugging crashes.
ObjcMessageSend is the guy that tries to dispatch a message. And if it was just about ready to dispatch a method to your object, well, those hidden parameters were all set up to be used. And I didn't point out explicitly before in the slide, but it was there. It said $r3 is the hidden argument self, $r4 is the hidden argument underscore cmd. So if you wanted to figure out, you know, I crashed an ObjcMessageSend, well, it's not Objective-C's runtime's fault. It was, it probably, what probably was trying to happen was that it was about to dispatch a message to an object that was deallocated.
And what you could do is print char* of $r4, and you could see what it was trying to dispatch. Or you can print the address of the object that was about to receive the message. But of course, if you try and PO the object at that point, it's probably dead, and it will raise another exception.
But this could be useful. It's useful when you're debugging to see what object was about to receive the message, you know, which object was dead, so on. And hopefully that's helpful. Normally, well, lots of times your crashes may be due to over-releasing an object. And one useful tip that may not be obvious that I just want to throw out is that you can use object alloc and turn on its retain-release recording, and you could use that to actually pair up your retains and releases to see if somebody's releasing it that probably shouldn't be.
Another idea for debugging crashes is to enable zombies. This isn't something out of Invasion of the Body Snatchers. It's actually a useful feature that you can turn on in the command line. It's not a default like the other defaults we talked about. It's an environment variable that you'll set. What you'll do is set the environment variable NSZombieEnabled to Yes. Then what happens is, for the most part, deallocated objects don't go away. They just get kept around in a state just enough that they can receive messages and raise errors.
What's going to happen then is that when you try and message a dead object, it's going to raise an exception like you see at the bottom. In fact, it's going to be nice enough to tell you where you could set a breakpoint to catch this. Now, one thing to note about this is that it works really well for AppKit-level and GUI-level objects, but it doesn't quite work so well for foundation objects, mostly for things that are toll-free bridged.
All right. And another thing that I wanted to mention for debugging that I think it��-- I'm pretty sure it's new in Tiger. You can probably pretty easily find this out. But it's a method called _subtree_description. And again, remember, these are methods for debugging only. They��-- they're meant to help you out in debugging. We don't want us to have to support these if we want to change them for some reason, use them. Just disclaimer.
This is very useful because it prints information about an entire subtree in the view hierarchy. For example, this is something I printed out from one of the earlier examples, the animating outline view. It's showing me that at the top level I have a scroll view which has a clip view as a subview, it has an animating outline view as a subview, and so on. There's a bunch of interesting information associated with it. Don't worry about trying to write down all this information really quick because this is also available on the download.