Frameworks • OS X • 53:19
Cocoa's rich set of APIs help you to refine your application and better integrate it with Mac OS X so that it stands apart from your competition. Discover how to add great features to your application and make it run more efficiently with just a few lines of code. Learn from the experts tips and tricks to help your application leverage the most from Cocoa to delight your users.
Speakers: Corbin Dunn, Mark Piccirelli, Kevin Perry, James Dempsey, Ken Ferry
Unlisted on Apple Developer site
Downloads from Apple
Transcript
This transcript has potential transcription errors. We are working on an improved version.
[Corbin Dunn]
Hello. Welcome to Cocoa Tips and Tricks, where we're going to talk about some cool Cocoa code. My name is Corbin Dunn, and I am a Cocoa Software Engineer, and I work on the AppKit team. So what's this talk about? Well, we're going to have a bunch of little quick tips and tricks on some neat Cocoa and AppKit things. It's focused mainly on AppKit classes. So if you're expecting something else, well, this isn't the place to be. Hopefully you'll learn something new. That's the goal with this talk.
Alright, so what are you going to learn and what are we going to cover? Well, we're going to talk about some TableView tips, nib tips, accessibility, image tips, document tips, PathControls, SplitViews, CollectionViews ... tons of stuff. So don't panic. We're going to actually have a lot of little sample code tips and pieces that you can just take back home and play with and big around with. It's associated with this session, so go ahead and download it and play around with those.
So let's jump right into it with some TableView tips. Links in a TableView. Something commonly requested is how do a get link inside the TableView and make it work like a regular link? Well, let's talk about how to do that. Well, the first way is you actually use an NSAttributedString to get it to look like a link and to have an actual URL embedded in it.
Then if we're going to use a regular NSTextFieldCell, we want it to work like a button, so we'll go ahead and overwrite some methods and do some custom tracking to actually make it so you can click on the link and tell the delegate that link was clicked on so the app can do whatever it wants. So how do you go ahead and create a link? Well, it's really easy.
You'll just go ahead and create an NSAttributedString, or in this case, an NSMutableAttributedString alloc initWithString, and the actual string you're passing here is going to be the title link. Then the key to doing it is using the NSLinkAttributeName. You don't actually see this. It's just the NS URL embedded inside the AttributedString. Then you can actually set other things to make it look like a link. You probably want it blue and underlined, so you can use things like the ForegroundColorAttributeName and the NSUnderLineStyleAttributeName to make it look like a link.
Something important with this, I'm talking about how to do it in tables, but it actually applies anywhere. So that's a great tip that you can use in just about anywhere in your application where you want a link. So you have to have a custom cell to make that link actually clickable.
So how will you do that? There's a couple of methods you could override, such as the end tracking, continue tracking and what not within a cell. But we're going to go ahead and override trackMouse inRect ofView untilMouseUp. And this is in a sample code called LinkTextFieldCell that you can download and play with. And we override this method because it passes some information we want, like the cell frame and what not.
So what we're going to do is we're going to go ahead and call super trackMouse. So when you go ahead and click on that button or that link, which we want it to work like a button, it's going to do all the tracking of the mouse until you let go.
And then at that point, sample code does a few more things, like make sure you actually click on the link and the text. We're going to gloss over that and look at the important details. So we're going to just grab the AttributedStringValue from the cell and kind of extract that in this LinkAttributeName to get that link. So you have that link.
You need to get this information from this cell in the table back over to the delegate or data source so it can do something with it. So what's the best way to do that? Well, a great way of doing that is to use blocks. You can use them as like a Click Handler, sort of like a target action, except the advantage of using a block is you can pass as many parameters or additional information as you want. So in this case, in our Custom Cell, the LinkTextFieldCell, we're going to add a Private Eye Bar called under bar LinkClickedHandler, picking a couple parameters, the NSURL and the ID of the sender, which is the cell. We then go ahead and declare our property for it.
And, again, it looks pretty much the same. The key thing to note here is that we're using copy in this property. It's important to use copy when you're using blocks. Otherwise, things may not work as you expect. And we're going to talk a little about this more in detail later. So now you have that LinkClickedHandler. Inside the cell, you actually will make sure that's not nil, and then just go ahead and call it. So that's a really easy way of actually invoking an action.
All right, so how do you set this cell up? If you're not familiar, this is a great tip here. Inside Interface Builder, control shift click, and it shows you a little view hierarchy of what you clicked on. So then you can go ahead and select something like the actual cell down there at the bottom. Here is a screen shot of it actually selected in the nib, the LinkTextFieldCell. And once it's selected, you can actually see in the Inspector, inside of Interface Builder, the identity tab has a little class section. And this is where you would actually put your custom class in.
In this case, we call it the LinkTextFieldCell. So that hooks the cell up to actually use it inside Interface Builder. So you have your cell hooked up. How do you actually do the work you want to do inside your delegate or data source? You'll probably implement something like tableView willDisplayCell forTableColumn, -row, which hopefully you're pretty familiar with.
And you can do things to make sure it's the right kind of row and column that you want. And then you can actually just set the block implementation right here in line, the LinkClickedHandler, which we call it when the user clicks on a link. You could do whatever you want in here. You could say have a link that opens up another part of UI, or it could put up a dialogue. Or more typically, you're just going to want to open that URL in the default application.
So the easiest way to do that, and another great tip, is to just use NSWorkspace sharedWorkspace openURL opens URL up in the default app. Really easy to do. All right, so those were a couple tips on how to use links. I'd like to bring Mark up on stage.
[Mark Piccirelli]
So the first thing I want to talk to you about today is NSDocument. And more specifically, overriding NSDocument methods correctly. NSDocument has a lot of methods for overriding. It's a very customizable class. But I want to let you know that you should try to override as little as possible. For example, don't override one of the methods whose names start with save, when instead you could override something more specific, like one of the methods that start with write. And if you want to find out like what calls what, see the documentation.
There's really great sequence diagrams in the section called "Message Flow in the Document Architecture." And also, see the comments in the header file, if you've never looked at them before. They are just full of details about what calls what and what all the default and limitations of NSDocument methods do. And the reason I bring this up is because we keep adding features to NSDocument. We're definitely not done working on it, that's for sure. And the less customization you do, the more features we're able to inject into your application.
As well as not overriding too much, override NSDocument methods correctly. This is something I've seen the write and save methods are passed the URL and type. But I've seen a lot of code that doesn't use them. I've seen instead code that invokes self fileURL and self fileType and uses those values instead.
And what's wrong with that is that the way safe saving works in NSDocument, number one, it changes from release to release. So where we're asking you to write your document during saving changes. And also because we do things like having you save to a temporary folder and things like that.
You've really got to pay attention to the URL that's passed in, and also the type name that's passed in. And another thing, when you override write methods, don't set values in write methods. Don't mutate your document unnecessarily. It works, but like not overriding too much, it's sort of a future proof thing. It allows us to add features without breaking your app, when you don't do that. Write methods should just write.
So we added blocks in the last release of Mac OS 10. But AppKit still has a lot of methods that look like this. This is an NSDocument method. SaveDocumentWithDelegate didSaveSelector contextInfo. And, you know, we follow that pattern everywhere that a class might show a sheet. And, you know, the work that the thing does isn't necessarily done when the method returns.
And that works great. But you have to add a method just to find out when the work is done. So that's a little more coding that you might not, you know, that you'd like to avoid. So with blocks, even though we haven't changed our API yet, you can add a few generic methods and use them over and over again to make your stuff work with this pattern.
And the way you do it, in a subclass NSDocument, for instance, add a method like this one, document succeeded with completionHandler. And that follows the method signature that's required of something that is passed to one of the saving methods or something like that. And as you can see, what it does, it's passed a block, that is the context info, and it just invokes the block and it calls Block_release when it's done.
And if you've written something like that, notice that's a class method, by the way, because you want to, you know, when you're watching your own code work, you want to make sure that something like that isn't touching any of your instance variables, just to cut down on the number of things you have to pay attention to while you're debugging the testing. But a class method like that, you can reuse it as is shown on the bottom of this slide. If you invoke something like saveDocumentWithDelegate, you pass it the class, and then you pass it the method signature for that method.
And then in the contextInfo, you call Block_copy, passing that a block. And you can do whatever you want there. And that way, you keep the code that, you know, is executed when saving is done really close to where saving is started. And the reason you have to Block_copy and Block_release is because the types of the contextInfo are all void star, so there's no retaining or releasing going on there.
Another bit of advice for apps that are document based is use file packages. If you're inventing a new file format, for example, and it's an application that allows the user to create documents that have great big attachments in them, use file packages, which, you know, Mac OS 10's trick for making directories look, to the user, just like files. So it'-- there's a lot of performance benefits to it.
And the code in Cocoa that lets you use file packages easily is the class NS file wrapper. It wasn't too popular before, but it got a lot of improvement in Snow Leopard. It got modernized, using NSURLs and NS errors and things like that. And also, we added this hard link optimization.
So when safe saving is done by NSDocument, for example, and it's writing the new revision onto disc, and then it's going to, you know, move that on top of the old revision in one atomic operation, NS file wrapper is smart enough not to just, you know, rewrite the entire contents, all the files of that file packages. It just makes hard links. So it's very quick. So check out the Mac OS 10.6 AppKit release notes for information about that. There's a lot there.
And the last thing I want to ask you to do for NSDocument is turn on AutoSaving. We added this feature a while ago. It's great. It provides crash protection and also protection against power losses and things like that. And, you know, a quick snippet of how you turn it on is there's a single method in NSDocumentController called setAutosavingDelay. And what that sets is how long should the document go unsaved after the user changed it? So check out the Mac OS 10.4 AppKit release notes for this.
And, you know, along with the, you know, paying attention to what you override in NSDocument, you know, that's for future proofing. This is actually also for a little bit of future proofing. If you make your application work with this Autosaving feature, it's going to work with things we add in the future, too.
And if you want an example of this, Text Edit, of course, has Autosaving. So in the preferences, there is a simple little bit of UI for just letting the user pick how often it auto saves. And the source code of that, of course, is available and developed, for example, is text edit.
So the next thing I want to talk to you about is exceptions and error handling in Cocoa apps. So, unlike a lot of frameworks in the world, exceptions are for programming errors. For starters, you certainly shouldn't use them for normal control flow. And if you want a benchmark for whether or not you're using exceptions too much, here is one I recommend. If you have a good test feed for your application, and you know you do, or at least you should if you haven't already, your test should be able to run, you know, successfully with no exceptions thrown.
That's how you know when it's really working, or when you follow the rules that we recommend properly. And, you know, there are, you know, outs or exceptions to these rules. You know, you can use them in your own subsystems. Just make sure that you don't-- no exceptions get, you know, thrown up out of your subsystem. So a little bit more advice.
Be careful about catching and not rethrowing. This makes debugging your problems harder, when an exception is thrown, something catches it and just swallows it, something went wrong, you don't even notice it during testing. So it's a good thing, as I mentioned, to do at some subsystem boundaries. For example, if you're using Grand Central Dispatch, you know, libdispatch, and, you know, you've passed a block to it, you'd better not let an exception get thrown up through that block. So catching and not rethrowing is, you know, a good idea in a few places. But in general, it's bad almost everywhere else. And at least log, you know, when you swallow an exception, so that when you're debugging your own code, you can see it happen.
And during your debugging, catching exceptions in the debugger is pretty easy. This is the function to break on, objc_exception_throw. And also in X Code, there's a menu item, "Stop on Objective-C Exceptions." And one little tip, when you do this, you might see Accessibility throw an exception or two when an Accessibility client has happened, or an Accessibility client has experienced an error. But in general, it doesn't happen that much. So reporting exceptions.
One thing that a lot of apps do, and it's a good thing for you to do too, is during your own testing and debugging, certainly, when an exception is thrown, let the user see it, if they're the kind of user that can report bugs on your application, put up an alert, you know, with the stack trace and all that. And the way you do that, there's a simple way in the AppKit apps. Override NSApplication reportException. And the sample code for this is, you know, up on the website.
When you subclass NSApplication, by the way, to override something like that, you have to, in your info P list information, tell AppKit what the name of the class is to use for the application. And it's down here. It's the Principal class. So that was exceptions. Now, there's a whole other thing; errors. In particular, the AppKit or the Foundation Class NSError.
And the errors are for showing to the user. They're really supposed to be presentable to the user, you know, right away, when they're returned by some method. So we use them in a lot of places in Cocoa now, NSDocument and NSFileWrapper and NSData and a whole bunch of places. And, you know, everywhere we do that, we strive to do something, you know, put strings in there, that if you show them to the user without doing any customization, the result will be pretty good. So the presentability of NSErrors is very important.
And the way you present errors to the user, there's two methods. They're pretty handy. They're on NSResponder, presentError modelForWindow delegate didPresentSelector contextInfo for putting up as a sheet, and this is another good place to use that tip for how to use blocks. And another one, presentError for presenting them apmodally. So just like with exceptions, don't swallow NSErrors either. Show them to the user. So that's it for me. I'll hand it back to Corbin. Thank you.
[ Applause ]
[Corbin Dunn]
Thanks, Mark. So I want to do a couple of Nib Tips. If you aren't familiar with it, NSWindowController and NSViewController is a great way for taking things like separate windows and separate views and dropping them into another nib to help kind of organize your application really well. It's really easy to use those. You just alloc initWithWindowNibName, and you can load to Nib with the WindowController.
The cool thing about this, top level objects are automatically freed for you, and it's really easy to use. Now, what if you want manually load some nibs? What and how do you do this? Well, why would you do it? You would probably want to do this if you have say a view and a nib and a want to kind of use it like a factory. You want to cash the nib and just pump out one view, another view, another view and another view, or Windows or whatever else you want. So it's really easy to do that. You can just go ahead and alloc initWithNibName of the actual nib and say cache it.
Then you can just do instantiateNibWithOwner topLevelObjects, and you can pass it to whatever owner you want. And that owner will then get all its outlets hooked up at that time. So in this particular case of this code here, the owner has a _secondWindow outlet hooked up to a window. And you could do something like makeKeyAndOrderFront to show it.
Now, we could go ahead and call out again, nib instantiateWithOwner topLevelObjects and pass the same owner in, because we want to reuse the same owner for some particular reason. At that point, the previous outlets will be overwritten, and you can reuse that same outlet for the actual new instance of the window and do things like show that window again.
Now, if you are doing something like that, it's important to know what happens in awakeFromNib. So that owner object is probably in a nib itself. So if we add some code that goes ahead and logs how many times awakeFromNib is getting called, let's take a look at what we'll actually get. Well, the owner is in a nib, is it's going to get it once. And then we pass that owner as the owner, or sorry, pass that object as the owner to two other calls in that previous little example.
So I would expect to see this print out a log three separate times; once for the owner itself, and twice for when it was actually passed as the owner. It's important to know that just because awakeFromNib might be called more than once. Now, if you are manually loading nibs, it's important to know that all top level objects you have to release manually. So that was a quick on nibs. I'd like to bring my colleague, Kevin, up to stage.
[ Applause ]
[Kevin Perry]
Hi there. I'm Kevin Perry, also on the AppKit team. And I have a couple of really quick tips on three different classes in AppKit. And since Corbin just talked about nibs, let's talk about Nib-Based CollectionViewItems. NSCollectionViewItem is the class that acts as a controller between the represented objects in a CollectionView's content, and the views that are represented visually in the CollectionView for each of those items. And in Leopard, it's an NSObject, a subclass of NSObject. And another role of NSCollectionViewItem is also to replicate each of the item views from a prototype view.
The way this worked in Leopard is that every time you added an object to your NSCollectionView, you would actually, we would actually archive and unarchive that prototype view and attempt to copy over all the connections and bindings from the prototype view to the item view. Now, this process sometimes dropped a few of those connections or bindings or did them inappropriately.
So in Snow Leopard, we have a new approach. We made NSCollectionViewItem as subclass of NSViewController instead. So we can use a nib to contain our prototype view. And when we add objects to our CollectionView, we instantiate that nib for each one and get a perfect replication of that prototype view with bindings and connections all intact.
Now let me illustrate how you would move from the Leopard style CollectionViewItem to Snow Leopard Nib-Based style. In Leopard, you would have your main nib, which contain your CollectionView and had a connection to the item prototype, which had a connection to your prototype view. And, in turn, that prototype view would be bound back to the item prototype and the represented object that it contains. So this is Leopard. To go to this new Snow Leopard style, you will create a separate View nib. And in that nib, you'll change the File's Owners class to NSCollectionViewItem. And this will represent the individual instance that's associated with a single object in your CollectionView's content.
What you'll do next is cut and paste your prototype view over to that new nib and redo all of the connections and bindings with the File's Owner. The last thing is to set the nib name of the item prototype, your main nib, to be that of your new View nib. And it's really that simple. You'll start using this new approach immediately.
Now, what about Leopard compatibility? I've got a little trick for that, as well. One thing you can do is override NSCollectionViewItems copyWithZone method. And the first thing you're going to do is alloc init an instance of your subclass. It's important to note you're not going to call super here.
And from somewhere, you're going to get an NS Nib. Like Corbin talked about, we can use a single NS Nib as sort of a factory for multiple instances of the same view. So you'll probably just want to have one NS Nib instance for your subclass. And you're going to instantiate that nib with the NSCollectionViewItem subclass instance as the owner for that nib. And that will set up all the bindings and connections properly for that instance. And then just return it.
Now, in Snow Leopard, loadView is a method on NSViewController, which, in its collection view item, will inherit. And loadView is where NSViewController attempts to load a nib. So you don't want nib-loading code in two places. So one thing to get around that is to override loadView to do nothing. Another option would be to make copyWithZone conditional only on Leopard. Let's move on to another class, NSPathControl. The first tip is that such a class exists.
It's a relatively new class, and I think a lot of people aren't familiar with it yet. But it's a very convenient class for displaying file paths to the user. As you can see here, we have a single cell for each component in the path, which is showed sequentially, and throw away some of the information that the path components at the beginning that the user usually isn't interested about, like slash users. We also grab the file icons from the file system automatically and use the localized file names.
The PathControl has some really great animation, so it still remains usable, when the amount of space you have in your user interface is limited. Now, there's three different styles of PathControl, as well. You see, this is the bezel style. We also have a non-bezel style and a popup button style.
The next tip is that NSPathControl is useful for more than just file paths. If your application has any sort of hierarchical data, which your users can navigate through, you can show that current path that they're looking at with an NSPathControl. You can do that by using setStringValue and passing any string, which has forward slashes that delimit the different components in the path. And to set up an image, you just call setImage on each of the NS pathComponentCells, since we're not going to be able to grab those icons automatically from the file system for you.
You can also do custom drawing in a PathControl by providing a custom pathComponentCell. And you can do layout with rectOfPathComponentCell:withFrame:inView if you want to do custom layout. Alright, let's move on to NSSplitView. NSSplitView is often used to implement sidebars, like we see here. We've got two sidebars on either side of the window and a main content view in the middle.
And when you resize this window, you're going to expect, usually, the sidebars to remain the same size and the ContentView to resize, as seen here. Now, by default, when you resize a SplitView, the AdjustSubviews method is called. AdjustSubviews attempts to resize all of the subviews proportionally to each other. In this case, this isn't what we want. So in Leopard, before you would have to implement the SplitView resizeSubviewWithOldSize delegate method. And the result of this was you essentially overriding all of the AdjustSubviews methods implementation.
You would have to do all of that yourself, as you see here. This isn't an insignificant amount of code. So we wanted to make this a lot simpler to do. And we did that in Snow Leopard. By adding this delegate method, SplitView should have just sized at Subview.
This method allows you to simply specify a subset of your subviews, which should be resized by the AdjustSubviews method. And here, we see, for this case that I showed you before, we returned yes for the middle view only. And that's all we need to do. It's very simple. All right, so let's move over back to Corbin.
[ Applause ]
[Corbin Dunn]
Cool. Thanks, Kevin. So let's do a couple quick Objective-C Tips. Now, you could have static methods in Objective-C. And you're probably thinking, wait a minute, what are you talking about? Well, really all I mean is you can just have a Static-C function inside of your implementation section here. Why would you want to do this? Well, you might want to take advantage of some compiler time optimizations, like inlining code or some other things which help with performance or data abstraction and hiding the implementation of a method.
Now, what's interesting about this, notice that in this method, we are accessing the self-dereference_window, which is a private Ivar in the class. Now, this compiles fine if you have this C-method inside the App Implemenation section. If you have it outside of that, you'll get a compile time error or warning, depending on how you have things set up. So that's a cool and quick great tip on how to actually do static methods, and calling them, of course, just like call a C-method, where you pass the self and any parameters you want.
Another quick Objective-C tip, OBJC_HELP. If you aren't familiar with this, go ahead and set the environment variable exportobjc_help to yes. Run any Cocoa or Objective-C application. And you're going to see a ton of other Objective-C run time variables that you can set. If you set any of these at the run time, they will actually show you more cool debugging features enhancements, I just recommend remembering OBJC_HELP and looking up the others if you need some stuff.
All right, so I'd like to do a quick demo. Okay, so I'm going to show a couple of the tips I like to use for actually developing inside of AppKit and things I do all the time. First of all, take a look at this class here, where this is the TableView links sample code, where it has the implementation and NSTableView delegate and the TableView data source. If I go to the implementation section, a real cool tip.
If you type dash and start typing in prefix like TableView, you can see all the TableView delegate methods, because Xcode knows that you actually want to implement those. And you can easily select one and it will toss in the implementation declaration, so you can just instantly add it without having to actually go to the header and type everything and getting any typos. So that's a great tip. Another cool one that I like is I use #pragma mark a lot. A dash will be a separator. And you can use #pragma mark and some actual title string.
And what this does, it's really small font, so it's hard to see, but inside of the little method popup here in Xcode, it will show those names and sections that you actually add. It's a great way for organizing your code. A great tip for here. This is actually a type selectable. So if I want to go to a number of rows in TableView, I just start typing number of rows and actually go to it.
Another tip is I'll frequently be working on a wim.m file, and I want to go to the implementation of a method, and not any calling locations of it. So frequently what I'll do is I'll hit Command F and type the right paren and then the method name that I want to go to. Select number of rows.
So by using that and specific code formatting that I always use, I can instantly go to just the implementation of the method really quickly in one particular file that I'm working in. All right, now I'd like to bring up my colleague, James, to the stage.
[ Applause ]
[James Dempsey]
Thank you, Corbin. So I'm James Dempsey from the Cocoa Frameworks Team, and the next couple of tips are about the secret life of your app's user interface. Because I know if my app's user interface was out running around with some other process behind my back, I'd want somebody to tell me about it. So let me explain.
So now, when we think about our application's user interface, we tend to think about its visual representation. But your app's user interface is a whole lot more than just a bitmap and a frame buffer. I mean, this is also a bitmap and a frame buffer, but it doesn't have anywhere near the density of information that's conveyed by your app's user interface. Because your app's user interface is really this collection, this rich collection of structural information, functional information, semantic information, all sorts of information that's all conveyed to the user by just a particular arrangement of color values.
So that's the life of our user interface that we're all pretty much familiar with. But now let's think about the experience of a blind user. Now, suddenly a bitmap is a pretty poor way to communicate all of that information. And for that particular reason or that primary reason, every application on Mac OS 10 provides another representation of the user interface. And this representation can be accessed programmatically from another process.
It is a structured and explicit representation of all of that information that we saw on our app's user interface. And I call it the secret life of your app's user interface because you may not know that your app's been spewing this stuff out all along. And spew, I mean delivering. So your application is a server for user interface information.
And other processes on the system, such as the screen reader voiceover, use a client API to make requests of your application, tell me about your UI, maybe to click a button or set some values, like moving a window around, and your app sends back a response. It's a pretty straightforward client/server relationship.
Now, voiceover ships with Mac OS 10, but those client APIs, they're public. So there's a variety. Once you have this representation of your app's user interface and other process, that's pretty useful. In fact, gooey scripting relies upon this, as well as numerous other tools and utilities available from Apple and third parties.
So now that you know that your app has this secret life that you might not have been aware of, what do you do about it? Well, the first thing is if you're using standard AppKit controls, the frameworks provide great accessibility support. Now, even if you're doing so, any custom images, we don't know what it's an image of. And so adding descriptions is a great thing to do. And I'll show you a quick demo of how to do that really easily in Snow Leopard.
And then if you have custom views in your app, we don't know what you're doing in that custom view exactly, so use the NSAccessibility protocol to reveal the interior of your custom views so that voiceover and things like gooey scripting know what's going on. And with that, let's jump into a couple of really quick tip demos. So first, image descriptions. Now, what I have here is a small little demo app.
And it shows reviews of appliances. So across the top, I have different appliance categories. And then for various products, ratings in separate categories. Now, what I have here is a slightly modified version of our Accessibility Inspector, which is going to tell us if the thing I'm pointing at on screen has an accessibility description. And as you can see here, nothing.
So a voiceover user who downloads this app is going to think it really sucks, because to them, it really does suck. Because none of this information is available. Now, because image descriptions are such an important part, we've tried to make that as easy as possible. In Snow Leopard, we've added an image and accessibility description property to NSImage.
You set it there, and it will show up wherever it shows up in the interface. But in addition, since so many of the images we use are named images, we have a specially named strings file, accessibility image descriptions. The keys are just the image names that you're going to call by image names.
The values are the accessibility descriptions. You pop this strings file with the particular name into your project, you recompile. And now this is actually a great app for voiceover users, because all of these images are completely accessible. Feel free. Please, clap.
[ Applause ]
Okay, now, one other tip here. And this is a sample app called Dicey. It's a little dice game. And it is a code sample we've had for a while that shows you how to make a custom view accessible. This dice area is a custom view. And, again, for a voiceover user who's relying on that accessibility information to drive the app, this app is, if we didn't make it accessible, would be useless, because they wouldn't know any of the values of the dice.
So that code sample, and many others that show you how to make your custom views accessible, are already available. Please check them out. Now, if you do go and make-- or when you do go make your custom views accessible, there's a nice little development benefit as well, and that's user interface automation testing. Now, what I have here is an AppleScript that's using the system events AppleScript dictionary to drive the user interface automatically. And this is handy for doing stress testing of your app, for doing user interface testing. Oh, sure, play again.
User interface testing for parts of your app that maybe users don't hit as frequently. And in addition to AppleScript, using the scripting bridge, you can also do the scripting in Ruby and Python. And so those are two accessibility tips and a heads-up about the secret life of your app's user interface. Thank you.
[ Applause ]
[Corbin Dunn]
Cool. Thanks, James. So I want to talk about blurry views. So what is this? And this is a blurry view, but note that this image is kind of simulated. It's really not that bad. But have you seen this before and wonder why your views are just slightly blurry? What causes that? Well, here's what we want it to look like, nice and crisp.
So if we take a look at the cause of that, it's because we have non-integral pixel alignment. So what that means is if you look at the frame for your view inside a pixel coordinate system, the X and the Y is probably something like 200 1/2 or 200 1/2 and what not. And that's the cause of why that happens. How do you fix that? Well, there's two easy solutions that are resolution-independent.
The first way is to use centerScanRect. And so what you'll do is you'll just grab your imageViews frame. Now, your frame is with respect to your appearance coordinate system. So you'll grab your superview and do a centerScanRect on that frame, and it gives you a resolution-independent frame that you can then set back on imageView, and it fixes the problem.
Sometimes centerScanRect might not do what you want it to do. It might round or seal or floor it not in the direction you want it to go. So if you want more explicit control, here is another way of doing that. Again, you grab your frame, you convert it to pixel space using convertRectToBase, again, on the superview. At that point, you can actually do your floor, round or ceil or whatever you think you need to do to round it onto pixel boundaries.
Then you can convert it back into your view's coordinate system by using the convertRectFromBase. And then, of course, set the frame. Now, you might be wondering, well, why do you actually have to do that, convertRectToBase and convertRectFromBase back and forth? Well, again, it's important for resolution independence. All right, now I'd like to bring my colleague, Ken, up onto stage.
[ Applause ]
[Ken Ferry]
Thank you, Corbin. Thanks, Corbin. So I'd like to talk about images for a little bit. The first thing I'd like to look at is flippedness. The issue with flippedness is that if you have a flipped view like this and you've got to do some custom drawing in that flipped view in your drawRect and you have images, you're very likely to come up with this. And so the question is, what do we do about it? So as a 10.6, this is not bad. We added a drawing method on this image that takes explicitly a parameter respect flipped.
If you pass the S for this, then it will adjust the context for its flippedness before drawing the image, draw the image and put it back. So that's easy. And if it is that easy, why is this worth talking about? Well, unfortunately, because there's a lot of existing code in the world, that doesn't really do this quite right.
You'll see people use methods that start with composite. And you'll also see people try to use setFlipped for this. And these don't actually do what you want them to do. They're both depricated as of 10.6, though you should-- they were sort of equally depricated before. We just didn't say they were depricated.
So it's not new. You don't want to use these. They may appear to do the right thing sometimes, but sometimes they won't, and it's not really what you want. In fact, just the fact the composite behaves differently from draw in the first place should be a tipoff that something is a little bit strange. Okay, for more information, see the AppKit Release Notes, please. Okay, let's talk about a mutation a little bit, okay? So suppose that you did go and take your image in your flipped view and you called setFlipped yes on it. And sometimes that will look like it works.
Sometimes it won't, but sometimes it will. But after you've done that, it's pretty common, unfortunately, then to go happen to look at some other part of your user interface, you pull down a menu, and suddenly there's an image that's upside down. And unless you happen to do these within three minutes of each other, you might not realize there's any connection there, which can be pretty frustrating, because it looks like a framework bug. And the problem, of course, is that it's the same image that's being used in two different places. So when you mutated one, you affected the other, and that's not what you wanted to do.
SetFlipped is depricated, but this applies to all mutations, including set size, and also lock focus on this image is a mutation. So how are you supposed to-- okay, so I'm saying you shouldn't do this. So how are you supposed to use these mutating methods? Well, the answer is that basically you aren't. We think of them as being part of the setup of an image. So you would alloc and initialize an image, set a bunch of things to configure it, and then you stop touching it.
Or if you need to-- if you have an existing image that you need to-- that you're in a sort of situation where you would copy it, modify the copy that is still just local to you, and then stop touching it. If you just receive an image in some method that's already sort of connected to your application at large, you'll want to treat that as immutable. That's the intended model.
Okay, so last for today, I'd like to talk about performance a little bit. Pretty much everything that has to do with performance in NSImage stems from one rule, which is that if you're doing the same drawing, you'll want to be using the same image. And what does that mean? Well, suppose that you have a window, like this one on the left here, and you're live resizing it, all right? So the images are drawing over and over and over, images that look the same, like everything you see in this window. As a user, you cannot tell if one of two things is happening. It's possible that every single time an image draws, it's actually a new image instance.
Somebody alloc and initialized an image, make locked focus on it, whatever, drew it. Or it could be that the developer just called setImage on some cell, and it's just one image that's just sitting there being drawn over and over. From the user's point of view, you can't tell unless the app is slow. Because the second case is the fast one and the first one is the slow one. And this is quite obvious if you start looking at it with Shark and such.
And the reason for this is basically caching, and all the way down the system. Certainly, every time you draw say a CG image, it will cache the color match data against wherever you're drawing it. Or we might, in some circumstances, we actually need to take that data and upload it to OpenGL.
And if you're always making new images, all of this work is happening every single time. And the drawing system is sort of shockingly fast, but it's shockingly fast when the cache is hit. So be careful. Okay, that's pretty much all I want to talk about today. If you would like more information, there are a couple good sources.
You can take a look at the AppKit 10.6 Release Notes. And also, we gave a talk about NSImage last year at WWDC that was actually quite in depth. The reason for this is that NSImage was actually pretty much rewritten for 10.6 with a focus on transparency. It should behave the way it looks like it's behaving, which required some deprication, and performance, and also impedance matching with core graphics. So please take a look at those resources, if you can.
[ Applause ]
[Corbin Dunn]
Thanks, Ken. So it's me again. And I'm going to talk more about TableView and give a couple more TableView tips. Variable row heights. If you aren't familiar with this, we have a delegate method to do this now, so you don't have to override a row or what not.
So how do you actually do this properly and what's the fast and easy way to do this? Well, you can implement the TableView Delegate Method, tableView heightOfRow and return the appropriate row height. So the easy way to do that is just go ahead and ask the TableView for a prepared cell at whatever column you want to actually measure, and the particular row that we're asking for the height of. So that gives you a cell ready to draw or do whatever you want with, which has everything set on it appropriately.
So what you want to do is you want to create a rect that has a specific width for that particular column or whatever else you're measuring. In this case, we're using _tableColumnWidth, where we're caching the width of the column we want to measure. We're going to give it that constrained width and an unconstrained height. So we're using CGFLOAT_MAX to just give it big large height.
And with that particular rect, we're going to ask the cell, how big do you want to be? What's your natural size? So we're going to say cellSizeForBounds with that constrainedBounds on the width and see what it says. Then we'll just go ahead and return that actual height that it wants to be. Great, easy want to implement variable row heights. Now, when you actually want to change the heights, you need to tell the TableView.
Every time that delegate method you just saw is called, you need to return the same height. TableView may or may not cache the value. So you will need to return the same height until you tell it that the actual height has changed. So a great place to do that is probably to implement TableViewColumnDidResize. So when you go ahead and resize the column and the user lets go, this delegate method's called.
At that point, you can grab the table column with whatever identifier is being resized and get the width and store it in an Ivar, and then you can tell the table, note height of rows with index has changed, and pass the index set for all the rows in the table to let it know it needs to recalculate all of the heights. So that's a great fast and easy way to implement variable row heights. All right, so in summary, we had a lot of tips in this application, or in this talk, and hopefully you learned a few new things.
If not, go ahead and download the sample code associated with it, because a lot of these tips have little tiny sample code apps that you can play around with and digest and figure out. So download that and play with it. Now, I'd like to have a--
[ Applause ]
[ Applause ]
[James Dempsey]
Good afternoon. How is everybody's week going? Good. So this session, we had a lot of kind of shorter tips and tricks. So I thought I'd bring in appropriate instruments. And very often, we were telling you in the session talking about a lot of wonderful things that you can do. I thought, for a change, maybe a little song about some stuff maybe you shouldn't do. So let's do it.
[ Music ]
I have to introduce, of course, the breakpoints. We're down to one breakpoint this particular year. Victor Alexander, the best slide advance man in the business on keyboard. Big hand for Victor.
[ Applause ]
And just to note with Victor, because we've been doing this for many years, people say, why is he the best slide advance man in the business? The reason is that he usually gets to slides about 23 minutes before we walk on stage. And he does a wonderful job. So another round for Victor.
[ Applause ]