Mac • 1:05:55
iPhone OS and Cocoa Touch have already enlisted thousands of new developers to Apple's platform. The UIKit skills cultivated for iPhone programming translate extremely well to adopting the Cocoa AppKit framework for Mac OS X development. Learn how easy it is to add the growing Mac user base to your list of target customers.
Speakers: Chris Parker, Kevin Perry
Unlisted on Apple Developer site
Downloads from Apple
Transcript
This transcript has potential transcription errors. We are working on an improved version.
[Chris Parker]
My name is Chris Parker I work actually on the iPhone Frameworks Team right now and we'll be talking a little bit about taking all of the skills that you've learned as an iPhone developer and applying them to programming on the Mac for the desktop. How many of you have actually done some Mac programming on the desktop before? Cool, so like 25% or so that's% a great number thanks for getting started.
We're going to talk a little bit, you know, basically you spend a bunch of time trying to get your application to fit on an iPhone, a little tiny device, memory constrained, it has some UI constraints based on the screen shape and the screen size and you're going to move up to programming on the Macintosh and Mac, you know, has up to 8 cores, it's got 30 inch displays hooked up to it and there are a lot of different things that inform the UI framework about dealing with larger screens and things like that. And just, also, the event models are different and there are some gotchas and we're going to cover a lot of those.
Some of the things that should be very, very familiar to you already, for instance, these guys the square brackets right, if you're new to programming for the iPhone you suddenly realize these weren't for array dereferencing anymore these are the square brackets for Objective-C message sense it's the same language on both platforms. All of the tools that you already use to build your iPhone applications, so Xcode for managing your projects resources and all of its code and building and debugging within Xcode, all of that's pretty much the same.
Interface Builder has -- not only does it have all the iPhone UI elements built into it and all the UI smarts to be able to build interfaces via drag and drop it has all of that stuff for the AppKit as well on the Cocoa side of things on a desktop.
And Instruments, basically, the same program, you know, so the workflow for building your code and developing your interface and instrumenting your code and figuring out how everything works and seeing where your performance problems are all of that workflow is basically all the same, so you're already very familiar with how to build up an application on the Macintosh.
You heard Bertrand talk about sort of the core layering that we use and, you know, there's Core OS and Core Services and the Media Frameworks and things like that and on the iPhone Cocoa Touch is made up of UIKit in the Foundation and a number of other frameworks and pretty much that diagram stays the same for Cocoa. There's a Cocoa umbrella framework that your applications will link against for the most part that by default contains AppKit, Foundation and Core Data and, you know, if you're using Core Data on both the iPhone and desktop that's a great opportunity here now in 3.0.
Some common things that are frameworks to both the desktop and the iPhone the Address Book framework is pretty much the same so that API is available in both places for dealing with your user's contact information that they have for all the people that they've met and put into their address book.
The Security framework is the same so just like on the iPhone if you're handling user's passwords and things like that you want to store those securely in the Keychain so they're encrypted the Macintosh has the Security APIs available to you. Core Foundation and Foundation are both available in both places so if your model objects and a lot of your business logic are going to be written as Foundation based objects, NSObject descended classes all of that code you'll be able to largely reuse probably without modification.
CFNetwork is also available so if you've been doing custom network loading with, you know, the CF HTTP, APIs things like that, that API is also available on the desktop. The System Configuration on the desktop, the framework there is a little bit different than the one that's on the iPhone.
The iPhone has a very small set of APIs the System Configuration framework on the desktop contains many of the things that you would normally find in a UI device and it also contains information about how many network interfaces the computer has and all the things that can be plugged into the machine as they come and go there are rich APIs for determining the state of a computer in System Configuration. All of the Core Audio APIs are there for the most part they're all available so if you're doing custom Core Audio filters or writing your own audio processing code you'll be able to use a lot of that on the desktop as well.
Quartz is available in both places so if you're familiar with using some of the Core Graphics APIs you'll be able to use those in both places. New in iPhone OS 3.0 Core Data is available on the phone so if you're going to be using a framework Core Data's been available for a long time on the desktop and now it's available on the phone so if you're going to be running your app on 3.0 and Snow Leopard you'll be able to share a lot of code there as well.
And moving from the phone the other way to the desktop is Core Location so if you have an application that uses some location-based services on the iPhone, Snow Leopard contains a version of the Core Location framework so you'll be able to use some of that code as well so you can have a location aware desktop Macintosh application.
It doesn't just say your desk is here, by the way, you know, if you have a laptop it tracks you. Mac feature availabilities some things are available only on the desktop or only on the iPhone but some of the big hitters here the Macintosh supports Garbage Collection, so we have an automated Garbage Collector on the desktop it's an optional thing you can choose to compile your application with Garbage Collection turned on and for those when Garbage Collection is on retain, release and autorelease become no-ops.
Alright, so we basically sort of swallow those and we track the lifetime of the object by tracking where it gets assigned and we keep track of all those write barriers and make sure that there's a reference to all of those pointers. And then a collector thread comes along and it cleans up any unreferenced memory so you can use Garbage Collection on the desktop if you want but it's not available on the iPhone. And the Blocks Language Construct, you know, you heard Bertrand and Bud and some of those guys talk about Grand Central Dispatch and blocks and this new language construct those aren't available on the iPhone but they are available on the desktop.
Some of the API that only takes blocks on the desktop won't be available on the iPhone those are all guarded by these availability macros and so you'll see things like Foundation uses the IF NS blocks available, # IF to be able to keep that code from appearing and you'll frequently see IF Target OS Mac and not Target OS iPhone and that's your guide to be able to read the headers and figure out what API is available on the desktop only and what APIs available in both places. Generally if something's not decorated it's available in both places.
We make a big deal in all of our documentation and in a lot of our sample code and things like that of writing the Model View Controller paradigm, so if you've been writing your UIViews and you've got a lot of Model code underneath and the Controllers mediate between those 2 a lot of your Model code is really going to be completely reusable so you should be able to take most of that View code and rewrite that but keep all of your Model logic the same.
So let's talk a little bit about Views and Windows and Events. In the AppKit on the desktop NSView is the base class for views and UIView is the base class for all UIViews in UIKit. They both are responsible for receiving and handling events so when you construct your subview tree, the views take care of figuring out where the event happened and sending the events up the responder chain appropriately but this is where a lot of that machinery starts.
They're both responsible for drawing so there's a draw rect method and you can draw when you get the draw rect message but they're both set up for drawing. There are some differences though one of them is UIView's origin is 00 in the upper left so when you're holding the device the origin is up here in the upper left-hand corner.
NSView's origin is actually in the lower left and that's just a heritage of the AppKit but it has some rendering benefits depending on which direction you're drawing. UIViews always have layers so if you've been doing iPhone programming for a while you're familiar with the idea of how the layer-backed drawing works.
UIViews always have a layer NSViews opt into a layer so the drawing model in the AppKit is a little bit different due to where the backing store is owned for drawing. And in UIView subviews can draw outside the view bounds optionally a UIView you can put subviews into a view and the bounds of those views extend outside of the superview's bounds and they'll actually draw out there unless you say Set Clips to Bounds, Yes.
NSView's subviews always get clipped to the view bounds, so if you've been taking advantage of that feature in UIView you won't be able to do that in NSView. So here's the example of the origin, the origin in the UIView is in the upper left, NSView is in the lower left but there are times where that may not necessarily be what you want.
On the desktop the View class has this idea of whether or not it's flipped so this is the default implementation for isFlipped for NSView. If you override this in your custom NSView subclass to say isFlipped, Yes, the origin slides up to the upper left, right? Some views in the AppKit are flipped by default some views are not but the base class is flipped, is returning No so the origin is in the lower left. A bunch of NSView classes so NSButton, ScrollView, SplitView, TabView and TableView are all flipped by default so their origins are all in the upper left and that has to do with being, you know, a table view generally draws from the upper left down.
Some views will actually allow control over this flipped concept based on what the current context is or they have API for it so NSImage, for instance, will figure out whether it's drawing into a flipped view and draw appropriately. NSTextView and NSFont the text system figures out whether or not you're in a flipped view and draws also appropriately and, you know, ClipView and things like that also take this into account.
So there's a lot of documentation over what's flipped and what isn't, so sometimes you'll see something and you'll go, oh, it's drawing down here and I want it to draw up here, oh, well, it's a flipped view so I have to take that into account so you can find out whether or not views are flipped with the UseFlip API. Oh look it's this session.
Here's basically a Keynote window and we could kind of explode this to think about how the views are laid out, and we collapsed the actual Keynote decorations into one view but notionally that's a whole lot of views there. But the idea here is for a set of NSViews is that we render those NSViews in Z order to a backing store that's owned by the window.
Right, so remember when I was talking earlier about UIViews each having a layer and, you know, you've got a whole bunch of views in there and if you update a view in the middle of that just that view gets updated, none of the other UIViews get updated because the rendering tree just composites all of that together.
In the AppKit when you add something to a scene like say, for instance, we Photoshopped this person in there and we want to draw that into an already existing view sub-hierarchy, basically, what we have to do is figure out where that rectangle appears and for every view that's in the hierarchy, basically, tell that view that it has to invalidate itself in that rectangle and then render that whole backing store to the back again. The AppKit is very fast at this, there's a lot of horsepower to throw at this kind of rendering so we can render all the way back to a backing store that's owned by the window and this works out to be a big memory performance win for us.
You can, however, opt NSViews into what's called Layer-Backed Mode, so rather than having this backing store that renders all the way back to the NSWindow if you call setWantsLayer and pass it Yes what happens is all of the subviews get layers and now the invalidation model and the rendering model is pretty much the same as UIView, right? Rather than rendering all the way back to a backing store each view draws into its layer and then those layers are composited and when you move those around none of those views are getting redraw commands.
When should you opt in? You want to opt in really if you want to reduce redraw to make your animation smoother, so every time 2 views would intersect in the NSView drawing model, you know, there's an increasing number of pixels you have to draw and then it decreases when the views stop overlapping. You can opt into it in order to reduce some of that draw noise because the compositing engine takes care of it for you.
You also need to opt in if you're going to use CAFilters to basically decorate your views, so if you're going to put shadows on the views or do any of those other neat effects that you see in the Core Image Funhouse app or something like that. The CAFilters are all -- you'll need to use Layer-Backed views in order to use CAFilters.
The flip side to when do you opt in is when don't you opt in and the answer's most of the time, most of your drawing is gonna happen in your NSView subclasses. Draw methods and the AppKit is very fast at pushing all of those bits all the way back to the backing store for the window so you're not really gonna need Layer-Backed NSViews all the time and it does amount to somewhat of a performance win to not use it.
So what we recommend is if you are going to use it turn it on, use it, do your animation, schedule all of your behaviors and then turn it off again. Right, so you can dynamically opt views in and out of the Layer-Backed views but this makes it a lot easier to do some of your drawing a lot faster too.
UIWindow and NSWindow, so, again, there's another set of parallel classes here and UIKit, UIWindow is basically a container for views. In the AppKit NSWindow is the container for views that's very similar. In UIKit, UIWindow inherits from UIView, but in the AppKit NSWindow inherits directly from NSResponder so NSWindow and NSView are actually peer classes in terms of their hierarchies; this changes a little bit the things that you can do with an NSWindow versus with a UIWindow.
With the UIWindow there's no user manipulation, there are no controls on the UIWindow when UIKit creates one for you to resize the window or to close it or anything like that. In NSWindow we allow user manipulation of the window directly, so NSWindows can be resized we have the Resize control on the lower right or they can be closed or minimized or maximized via the controls in the upper left, so all of those things are things that you can do in NSWindow that you can't do with the UIWindow and it changes a little bit some of the things about drawing. UIWindows are always screen sized, when UIWindow comes up out of a NIB when we create one for you it's always 320 by 480, it's always the same size it's always full screen.
When you create one programmatically all of our documentation tells you, you make it full screen, so that's 320 by 480. NSWindows can be any size programmatically or if the user resizes them and the users resize windows all the time, you've got 15 windows you're probably doing it on Xcode all the time too, you know, you've got a whole bunch of windows open, you're moving things around, you're shrinking things, you're expanding things, you're trying to make your work environment more comfortable and that also informs how we do NSWindow work in the AppKit. Also, UIWindow you pretty much get 1 in most UIKit apps. I mean, you can use UIWindows as containers for other things and shift them back and forth but usually there's pretty much 1 window.
In NSWindow you can have many windows in a single application. Also, for event handling, there are some similarities between the 2 event mechanisms as well. There we go. UIEvents and NSEvents both contain information about input actions so they're both going to tell you everything about what happens as the user interacts with your application.
UIEvents contain UITouch objects pretty much or in actually iPhone OS 3.0 they might tell you about a motion event that happened as well but those pretty much are just about all of the events that happen as the user touches the glass and puts their fingers on the glass to do gestures.
And NSEvents encapsulates all kinds of different events, there, you know, not only does your user have a track pad on their laptop but they'll have keyboards and mice hooked up they'll have tablets with a pen and the pen might produce proximity events as it gets close to the tablet, so all of those events are things that you can find out about from NSEvent and it covers all of those input devices that basically can be plugged into the user's computer; so this is a far richer set of events that you have access to the NSEvent.
UIEvents are persistent through a sequence so, you know, as an event happens and as the user touches on the screen those touches build up into a single UIEvent and that UIEvent is the same event object the whole time. In NSEvent in the AppKit every time you get one of these new things happening like a proximity event or a touch or a key down or any of those things that's a new object for each event and those new objects are immutable, you cannot edit those objects they are just stand-alone value objects that tell you what happened.
And UIEvents, you know, you can cancel UIEvents, things happen and the state changes or the phone rings or something happens and those touches can be cancelled and in the AppKit pretty much you don't get interrupted by phone calls on your computer very much those mouse events and those other events can't be cancelled so you can choose not to handle them but you're not going to get API access to just say OK, we'll pretend this never happened.
There's also some touch support in the AppKit. Alright, UITouch and NSTouch both track information about touch events so everything that's happening on the track pad on recent MacBook Pro models the AppKit can find out about those via the NSTouch class. UITouch deals with an integrated sensor; the phone has an integrated glass sensor on the front that when the user touches it the place that they're touching is the actual coordinates in the screen.
NSTouch you're sort of one step removed from it it deals with opaque sensors so the track-pad, you know, you make a gesture here but you're not actually touching anything on the screen so the focus goes to either the responder or it may go to where the mouse cursor is, so there's a little sort of step removed in dealing with that.
Raleigh Ledet who is the Mr. Events Wizard for the AppKit will be giving the User Events in Cocoa talk tomorrow -- wait -- I hope tomorrow's Wednesday -- tomorrow in Knob Hill at 3:30 so if you do want to learn more about handling events directly on the desktop definitely go see Rolly he'll give a great talk on user events.
So let's talk a little bit about controls now. I promise this isn't all just me droning on up here about the differences it gets a little more interesting later. UIControl and NSControl, these are both the base class for control. So remember a control is something you tap on it or something happens and it sends an action, right? They both send actions when directed but UIControls you can have many actions each of which has a target and in NSControl you pretty much have 1 action and 1 target.
And there are basically 3 forms of the action selector you can have. In UIKit there's 1 that's just an action that gets sent, there's 1 that tells you the sender of the action and then there's action forEvent which is the sender and the UIEvent so you can do some multiplexing based on what events came in.
And in Interface Builder when you click on a control, in this case a UI button there are all these different events having to do with touch-drag and touch-entered and exited and things like that where you can find out about all these things, and not only do you find out about all of these things you can send each one of those actions to a different receiver, so many actions, many receivers. NSControl, this is the only action selector you get so you get one that says, this is the action and who sent it. That action can be key-down, key-up, things like that. This is what it looks like in Interface Builder.
The one exception to this in terms of getting more than one action, NSTableView has a double action so when the user double-clicks on a non-editable portion of a table view like a non-editable table cell or table column you'll get a double action and you'll get an opportunity to handle that event as well so you can double-click on something and it will tell you whether or not, you know, you'll get an action for a double-click.
So when you're writing a Macintosh application typically, you know, remember what happens in the UIKit, a lot of your applications are database driven or table-driven the table view looks like this you tap on the table view and you get basically a zoomed in or a detailed view of the data that represents that row that you've clicked on or that you tapped on -- I'm sorry.
On a Macintosh, you know, there are a lot of document-based applications and the user creates things with Text Edit they create things with Keynote they have pictures that they've created with different applications or that they've pulled off of their phones or their cameras and they can save these documents anyplace and there's a lot of support directly in the AppKit for document-based applications, and the main class that takes care of a lot of this is the NSDocument class and then the NSDocument controller mechanisms. And document-based apps basically the NSDocument helps you out with the reading and writing of data, it helps you out with atomic saves and some simple scripting stuff that's built into the AppKit and OS X. The atomic saves thing is really a big deal.
When you're handling user's data you don't ever want to lose the user's work, so when we save what we tend to do is we save in a location and then rename that save over the old location and NSDocument takes care of a lot of the mechanics of that atomic saving for you so that if something does go wrong. If everything goes right you save the document, the user's happy, you're happy, everything works great. If something goes wrong like the user doesn't have write access someplace or the file system's gone south on them or they've lost contact with a file server.
If we save -- if something goes wrong there we want either the old version or the new version to appear on disk but we still want the user to be able to save someplace else with the version that they have in memory, and NSDocument helps out a lot with those mechanisms as well. To show us a little bit about how all this works I'm going to ask Kevin Perry to come up. Kevin works on the Cocoa Frameworks Team and he has some demos on just creating a document-based app right in Xcode.
[Kevin Perry]
Thank you, so here we are in Xcode and we're just going to start off by creating a new Cocoa application that is document-based and we will just call it Demo App for this demo and we're just going to start off building and running it to show you just how much and how quickly you can build your own applications in Cocoa.
Cocoa really makes the simple things and common things that you do in your applications very simple to do and you can focus on the more complicated and more interesting things in your own work. So here we have our demo app running and we see an empty document window up here, there's not really much there but, you know, hey, we have a document window and we can create more document windows and close them and we can bring up a Save Panel here and attempt to save, you know, we haven't implemented saving yet but not only do we have the Save Panel we have proper error reporting when the save does not work properly and we have Open Panel as well and we properly can't open anything here because we haven't implemented that yet. So that's a lot of behaviors that you can get for free right out of the box with Cocoa.
All these behaviors, for the most part, are handled by NSDocuments, NSDocument Controller and NSWindow Controller various classes in AppKit and for the most part you'll just use these classes straight out of the box they will work for you and do what you need for the most part. But in those rare cases where you need to have custom behaviors you can, of course, subclass those classes and provide your own implementation. So we'll go back to Chris for a little bit more about NSDocument we'll build off of this in later demos but for now back to him.
[Chris Parker]
Thank you sir. So, absolutely no code in that, go to Xcode, Create a New, document-based application and pretty much you're already getting a number of default behaviors out of it, you know, so the windows get staggered, the untitled stuff appears, Cocoa really helps you out with a lot of the basic stuff so you can concentrate on your app.
If you're going to override things you need to actually get data in and out of the disk, so NSDocument has some APIs for reading your document, read from URL of type error and this is a pattern that you see a lot in Cocoa you saw it in CocoaTouch probably a little bit.
The NSError contains anything where we think that something can go wrong we'll try to give you an NSError return and that NSError will always contain basically if it's in the Cocoa domain it'll always contain something that's suitable for presentation to the user, and if we've done our job right that means that you'd probably never have to see Error-43 again or anything like that, really obscure error messages that don't help the user out. The other one is Read From File Wrapper of Type Error.
An NSFile wrapper is a class that basically encapsulates it abstracts out a hierarchy of objects and that hierarchy can wind up being written out to disk as a directory structure or you can flatten in NSData there's a lot of flexibility with an NSFile wrapper for editing documents and we'll take advantage of that in a minute. And a Read From Data of Type Error, if for somehow in your application you got an NSData that you want to turn into a full NSDocument in an instance you can do that with this method here.
The other half is writing stuff out, being able to write to URL of Type Error. You can get a file wrapper of the types of the NSDocument if you produce an NSFile wrapper this is how you'd get the NSFile Wrapper Instance out of that document and Data of Type Error so there are a symmetric set of APIs. There are some other things that Kevin's going to show you now in actually filling in that document-based application to actually save things out.
[Kevin Perry]
Alright, so we are going to now build off of this empty project and build a very basic RTFD Editor. RTFD is a file type related to RTF or Rich Text Format that also allows for embedding various arbitrary files such as images. So at the end of this demo we're going to have a simple but powerful rich text editor that supports image embedding and will have full save and load behavior.
So we'll start off we'll show you how easy it is to enable Garbage Collection we'll use that for this demo because that will just simplify things for our purposes. So here we find the Garbage Collection setting and we just set that to Required -- close that -- and now we're going to go in and start developing our interface.
Here we see this familiar empty window that represents our document contents, so we're going to come over here to the Interface Builder Library, which you should be familiar with if you've made applications for the iPhone, of course, and we're dragging in here in NSTextView and you see here that we've got a scroll-view already that this text-view is embedded in so that's something that Interface Builder sets up for you automatically. We're going to set this to size to fit to the window.
Now, we want to make sure that this text-view remains the same size as the window so we come in here to the Size Inspector and enable these auto-resizing flags. You see here a little preview of that and we can Command-click here in Interface Builder and see that live.
So now we're going to click here, drill down to our NSTextView and go to its Attributes Inspector. And you see there are a lot of various features that you can enable or disable on NSTextView with the click of a check box. NSTextView is really one of the gems of Cocoa you'll find it in most applications. Just because there are so many features that you can get for free that are common and expected now on Mac applications.
There's things like Rich Text, Undo, Redo, Graphics, Image Editing, Non-Contiguous Layout which lets you display large, very large documents very efficiently, Find Panel, Font Panel, Rulers, and so forth, but we're just going to enable graphics here for now so we click this check box. Now we need some way to let our documents talk to our text-view and we need some interaction there so we're going to come in here to the My Document header.
Make this monitor big. And just as a disclaimer we're going to take a little shortcut here, normally you'd have a proper model view controller set up for this but we're just going to connect our model, which here is, in this case it's the document's subclass straight up to our view, the text-view.
So we're going to create an IV outlet in NSTextView like so, we'll come back here to Interface Builder and here in the NIB the file's owner is what represents our NSDocument Instance so we Control-drag here from the files owner to the outlet and we see here that there's our text-view outlet that we just added so we connect it like so and we save.
Come back here to Xcode and go to the implementation of our document class and we'll implement saving and loading of our document now. Here we see the data of type and read from data methods that Chris pointed out earlier and for most cases this would be a sufficient place to implement this but since RTFD is actually a package type or a bundle type we can't easily represent it with just NSData we want to use an NSFile wrapper so we're actually going to instead of overriding these we're going to override write ToURL of Type Error and here we'll implement this like so.
Here's our text-view we're grabbing its text-storage and building an Attributes Dictionary with the RTFD Text Document type as the document type attribute. Then we're asking the Text Storage to create a file wrapper for us with the entire contents of the file and the attributes that we have here. And so that gives us back an RTFD file wrapper which we can then tell it to write to disk and we're telling it to do that atomically as well so we have some safe behavior there.
And we just return whether or not it succeeded. Implementing document loading is just as simple we override Companion Read from URL Type of Error and we implement it like so. Again, we're building a dictionary with the RTFD document type and we're using NSAttributedString here instead of NSFile Wrapper directly because NSAttributedString already recognizes and understands when it sees an RTFD file on disk it knows how to use NSFileWrapper to load that document. So here we've created the attributed string and we return whether or not that succeeded.
At the point of document loading our interface hasn't been instantiated and displayed to the user yet that happens here in Window Controller Did Load NIB. We get this notification after the NIB has loaded. So we need somewhere to push this attributed string into our text-view. We need just a simple instance variable to hold on to that, so here we call this NSAttributed String an Attributed String.
And here in Window Controller Did Load NIB if we have successfully created one we just take the text-storage and set the attributed string on it. Now that's all the code we need to write for this application just 10 or 15 lines of code here not very complicated, but there's one more thing that we need to do to tell NSDocument about the file types that we want our application to understand.
So here we open up the application's target window and go to the Properties tab. Here we see a table of all the various document types that our application understands and we're going to set the UTI or the Uniform Type Identifier to the proper con.apple.rtfd UTI. Now the Uniform Type Identifier abstracts all this information about file type extensions, Mime types and OS types so we can actually delete all those entries, but the last thing we need to do is tell it that it has actually a package format not a flat file there. So we'll go ahead and build and run again and we will demonstrate this working. So I'll start typing something here.
[ Silence ]
[Kevin Perry]
And you see here the Close button as I've typed now has this dot in the middle of it indicating that the document's dirty and that there are unsafe changes in there, and so when I try to click on that it informs me of these unsafe changes and asks me if I want to save them or not because they'll have undo, redo as you saw that was one of the things that we left enabled in our NSTextView and we also have, of course, Rich Text Editing so we can change the font.
And you see, also, I have a little typo here and this text-view has automatically found this typo for me and I can correct it just like that. All of this -- we haven't dealt with any of this code on other platforms this could include a lot of extra work.
So now let's see Saving and Loading working we will go to our documents here and save a document off. It seems like it works let's -- it's the moment of truth here we'll try and open it back up and there you go saving and loading works just fine just 10 or 15 lines of code and you see how simple it is to get started right away with your own applications in Cocoa. Back to Chris we'll find out more about NSDocument a little bit and other features that we'll add to this application next.
[ Applause ]
[Chris Parker]
So, yeah, 10 lines of code and a whole bunch of built-in behavior that's already provided for you by the AppKit so there's a lot of features there that you don't have to even worry about. NSDocument also supplies a lot of other behaviors as well, Autosaving, so if you're like me and you're saving you have the Command S twitch on the keyboard, you know, every few seconds you're always saving. If you don't have that twitch you can configure your documents to autosave automatically on an interval you set.
Alright, so you can save your users some trouble if, you know, the power goes out or if, you know, heaven forbid your app crashes something like they'll have an autosave version of a fairly recent version of that data. There's also some features in there that help you print, so, you can have printers hooked up to your computer as well NSDocument helps support that.
The Change Management with the little dot and tracking whether or not it's dirty or things like that and Error Presentation, right? All of the sheets and things when something went wrong or all of that is all built-in to the AppKit so you'll be able to do some very rich behaviors with pretty much no code at all. One of the most intuitive ways to be able to interact with things on the Macintosh, you know, you've been dragging, dropping files to copy them in the Finder probably since the day you opened up your Macintosh.
Drag and Drop is supported in the AppKit you generally have a source-view and a destination-view right, so you're dragging from one place to another and there's an operation involved in the middle there alright so the operation has to do with what it means to drag from point A to point B. The operations are pretty straightforward.
There are operations for Copy, Link, Move and, basically, what you're doing here is telling us exactly what will happen when a drop occurs is it a copy is it a move what do you support from the source side of the drag? The only method you really have to implement in order to get this is Dragging Source Operation Mask for Local and the dragging source API here gives you an opportunity for your custom view subclass to return what operations you support.
The ForLocal part tells you whether or not this is a drag within your application or whether it's a drag to another application, so you find out whether or not this is an intra or an inter-application drag. The destination API there are a few other source APIs about updating the images as they're dragged and you can customize what all of that looks like but these are the basics. The destination API is interesting it's draggingEntered, draggingUpdated, draggingExited, prepareForDragOperation, perform and conclude and these are where in your destination you can handle all the different events associated with the drag.
And that sender's how you find out, you know, you can find out what operations the sender supports and things like that by querying that via the NSDragging Info protocol. All of this stuff is pretty straightforward but there are a lot of classes in the kit that implement higher-level API to make it easier to deal with Drag and Drop and we'll see that in a minute.
One of the things I didn't talk about in the earlier section was table views and there are actually 2 classes UITableView in the UIKit and NSTableView in the AppKit. They both have very similar APIs one of them has a delegate, they both, actually they both have delegates; they both have datasources so that API should be very familiar to you.
UITableView mainly because of the constraint of the size of the screen, has many rows and pretty much 1 column where you're just going through a single column of information right? NSTableView -- hey, you know, we have 30 inch monitors hooked up to some of our computers or 24 inch monitors it's a very wide experience and so you can actually display many rows and many columns so you get much more -- an actual tabular interface in an NSTableView.
If you want you can set it to 1, you know many rows in 1 column but it doesn't happen often. UITableView deals in UITableView cells for individual items, and NSTableView deals with objects and it fills in NSCells for individual items so that the model for what you're asked to provide as a datasource of an NSTableView is a little different than what we see with UITableView.
So remember what happens with UITableView, you know, you've got your data model and the first thing we'll do is we'll ask Number of Sections in TableView, how many things are in there. And then we'll turn around and we'll ask for the number of rows in each section, so for each of those sections we'll find out how many rows they're in.
And then eventually you get asked to provide as a return type its UITableView Cell, TableView cell for Row at Index Path and you're actually creating a view and filling that view in with the data and then that view gets around it put into the view sub-tree so if you look at this you'll actually see views in there right? UITableView cells are a full UIView subclass.
The AppKit does things a little bit differently partly because you've just got so much data that you could do if you wind up with 600,000 views that kind of slows things down a little bit. So instead NSTableView uses an approach that involves a class called an NSCell So the first thing we're going to do is we'll ask you number of rows in table view, how many rows are in this table.
In this case what I have here is a datasource of restaurants that I like and I'm going to try and display them here in this table view. The next think we do is we go to the table column the table view goes to the table column and asks it for a cell that's appropriate for the type of data it's going to be displaying. In this case we have an NSTextField cell and this cell, basically, gets filled in when we call your datasource.
You'll get table view object value for table column row and you'll notice that returns an ID, that ID is the actual object that represents that entry in the table column and row. And the idea here is in this case it's an NSString and we're going to fill out that cell in the NSString and then we're going to take a snapshot of that cell with the NSString in it and we're going to display that, we're going to stamp it into the position into the NSTableView.
And then we're going to do that for every single item in that column, right? It happens a lot faster than this. So the next thing we do is now we're going to go, in this case, the table column parameter that you were getting called with for each of those things was the Restaurant column now what we're going to do is we're going to go and get a cell for the next column which is the Rating column.
And in this case what I did at Interface Builder was dragged out what's called a Level Indicator cell and we provide a number of these cells built-in to the AppKit. The Level Indicator is a thing that's basically a discrete or continuous display for either levels or capacity, things like that and you can set a bunch of different parameters on. In this case I had it set to what's called Rating Mode and that's a fancy name for drawing how many stars appears in here.
So I've assigned ratings to each of these restaurants and now what happens is we'll return the 3 or the 4, the NS number that represents it. When that hits that cell it's going to get reformatted every time table view object value for table column row gets called and that's going to get filled in with the various pictures of the stars. Alright, and if I don't give my work cafeteria and my mother's cooking the same rating I'm in a lot of trouble in both places.
So this is what it looks like when it's all filled in right, and this use of NS cells allows us to be able to display just really wide, really large amounts of data very, very quickly because what we don't have are a lot of heavyweight NSViews sitting in that table.
So here are a bunch of the different cells there are, you know, comma box cells, check boxes, pop-ups so a lot of the common things that you would put in tables, you know, Boolean values might be represented by check boxes, things like that, these are all built-in. There's a custom cell class so if you write your own you can still drag them out in Interface Builder to assign them in the table view. Kevin's going to show you just how easy it is to use NSTableView as part of this RTFD Editor.
[Kevin Perry]
So we're going to return to our RTFD Editor application here we're going to enhance it with an Image Library so that we can put our images directly from our application into our documents, and so we're going to use NSTableView to demonstrate how to do this. So let's start off by creating a new controller class subclass of NSObject we'll call it Image Library Controller and it will be responsible for populating our table view. So we have it implement here the NSTableView DataSource Protocol and we will now go over to our main menu -- close this NIB here -- our main menu NIB and we'll instantiate the Image Library Controller here.
So we find a custom object, drag that out and change its class to Image Library Controller. And now we need some place to put our table view that we'll have so we grab a panel, which is a special type of window. Now, since this is a garbage collected application we need to make sure that there are references to these objects so that they don't -- they're not collected by the background collector thread. So what we'll do here is we'll just add an NSPanel IB outlet called Panel and connect that -- semi-colon -- thank you -- connect that like so and we'll make the Image Library Controller the delegate of our application.
Another disclaimer this is just a shortcut for demo purposes you'll have in your own applications window controllers and view controllers and so forth to manage the lifetime of these objects. So now that we have our window here we will finally create our table view. We will do the same thing we did before with the text-view, again, it's already embedded in a scroll-view and we want that to auto-resize like so in the window and we'll click the Drill Down to our table view. Now since this is going to be an image library we're going to be containing displaying images a row height of 17 isn't going to be sufficient so we'll go ahead and bump that up to say 100.
And in the attributes we will make this a single column with no headers. We also need to connect this to our Image Library Controller and now here you see the datasource outlet which is what we want to connect to. So by default you see Interface Builder creates an NSTableView with a text-cell.
We want an image cell, obviously, so we just drag that right in and here you see a good representative example of what our table view would look like. So now we have dropped an NSImage Cell in this column. The datasource methods are going to expect an NSImage to be returned instead of, for example, an NSString. So now we'll go ahead and save that.
That's all we need to do for our interface. So now we have our controller object here, we have our view set-up, we need now a model. We're just going to have a simple NSMutable Array for now and call it Images, and now we'll come to the implementation of our Image Library Controller and in the init method we will populate that array, here we're creating it and we're enumerating the contents of a particular directory here on our desktop and for each file in that directory we're attempting to create an NSImage with it.
If that's succeeded we'll add it to our array, pretty simple. But now we need to give these images to the table view and we do that by implementing the 2 NSTableView datasource methods that Chris already talked about. So the first is number of rows in table view, well this is really simple we just return images count we have X number of images we want X number of rows in our table view. And the next one is TableView ObjectValue for Table Column and Row and we return just images objects at index with the given row we just have a single table column so we can ignore that parameter there.
So let's go ahead and run and here you see a bunch of images in our library already. Just, again, a few lines of code and we have created a fully working table view datasource. But currently we can't really make it do anything we need some way to get these images from the table view into our document here.
Chris also talked about drag and drop that's probably the most intuitive user interface for this so we'll show you just how easy this is to implement with NSTableView, which abstracts a lot of the dragging source and destination -- or the dragging source API that you have to implement. So back here in our Image Library Controller we will override table view write rows with indexes to Pasteboard.
So here we have to -- we're supposed to write our selected image to the pasteboard so it can be read at the destination. And so the first thing we do is we take the keyboard, the NSPasteboard parameter that we're given here and clear its contents so we have a clean slate to start out at.
And then we tell it to write objects and give it NSArray with a single object which is the image at the first and only index in this row indexes index set like so. Now write objects returns Yes or No for success and we need to do the same, as you can see here, from this datasource method so we return what it returns.
We build and run, once again, and you can see that drag and drop is just that simple it's a 2 line implementation here in our datasource and we have fully working drag and drop into our NSTextView. So another testament to the power of Cocoa and ease of use there. Back to Chris for more.
[ Applause ]
[Chris Parker]
So, you know, both table view and the text-view implement a lot of the dragging methods for you so, you know, you notice when you drag on the table and in the text-view the insertion point moves around to represent where in the text those images are going to be dragged -- dropped rather, so there's a lot of built-in behavior, again, that you're getting for the kit absolutely free at no cost to you.
Another interface that we use a lot is in the Finder this is just a search window there's nothing in it right now but when you type up in that search field, you know, Spotlight goes through and it uses the indexing information that's stored on your hard drive, on your user's hard drives and returns results in there it puts the document icons into the search window to find out what meets that criteria and that's all based on the Spotlight APIs that are in Foundation and in MetaDataQuery Framework.
But the Foundation versions of these classes are NSMetaDataQuery and NSMetaDataItem, so these are basically the way that you can programmatically be a client of the search APIs in your application right? Remember you user can save stuff any place in the file system so they may have trouble finding things occasionally or you may have trouble finding things or you may want to find things for them and Kevin's going to show you this in a minute but the main class is NSMetaDataQuery.
You program a query by giving it a predicate basically as something that evaluates to true or false that defines the criteria that you want to use for the search and it supports grouping, which is really interesting, so you can group by type. It's an asynchronous API right because, you know,, you've got 320G hard drives, 500G hard drives, terabytes worth of information in places. So the search starts and then it has to crawl through the index and find out where all that stuff is.
There's a delegate API that calls back and says, hey, I have results. What those results are NSMetaDataItems and those items are not actually the files themselves but there's a lot of information about those files. Things like when it was last opened, when it was last moved, the contents changed, the, you know, where it came from that kind of things, a lot of information in there including the path to the file itself. But, again, it's not the file itself. There are also some nifty features about NSMetaDataQuery that make it very easy to program with and it'll actually let us get rid of some code, so Kevin's got an example of that as well.
[Kevin Perry]
Alright, so we'll return here to this init method where we have populated our array of images. Now, if your user wants to be able to find images in, for example, various locations on the file system or images with particular properties or other meta-data about the images you would have to do a lot of work on this it would get a lot more complicated very quickly but we can use NSMetaDataQuery, use Spotlight to make that a very simple thing to implement.
So we're going to come back here to the header or our Image Library Controller and we're going to swap out the array for an MSMetaDataQuery. We're actually going to add, right now, over here, a property for it for reasons you'll see shortly. And we'll need to synthesize that here as well.
[ Silence ]
[Kevin Perry]
Alright, so now we can actually delete all of this code and we'll replace it with this. We're simply instantiating an NSMetaDataQuery and we just set a predicate which will tell the query which kinds of files we want to match. In this case we are finding any files whose content type matches image, so any image types that the system recognizes will match.
But we'll limit the search scopes to the single directory that we were searching before for this demo, and then we start the query to start getting the results. So NSMetaDataQuery has a results method that you can call to get an array of all of the files that it finds. But this results array actually contains instances of NSMetaDataItem which is a wrapper class around all of the meta-data that you might be interested in about the files that Spotlight finds.
For this we want not the meta-data on the files we want the actual data so we actually want a way to replace the contents of this results array which we can do fairly simply with NSMetaDataQuery and its delegate API. So we will here declare conformance to the NSMetaDataQuery delegate protocol and set ourselves here as the delegate of the query and then we implement --
[ Background noise ]
[Kevin Perry]
Did I not save that? There we go -- MetaDataQuery Replacement Object for Result Object, so the parameter here is the NSMetaDataItem and we want to instead return an NSImage which we initialize with the contents of the file that that NSMetaDataItem represents and you can get that file path by calling Value for Attributes and passing KMD Item Path, which is the Spotlight's constant for that meta-data information. And there we go we've created an NSImage and now that will be put into the queries results array instead of the NSMetaDataItem.
So one of the great things that Chris was alluding to about NSMetaDataItem is that it supports bindings. The Results Array will be updated automatically when the file system changes or when Spotlight finds additional files that match the predicate and the Results Array is specially designed for binding so that when connected with the proper bindings-aware controller all that information can be pushed very easily to your views without any additional code.
And we can actually replace bindings, we can replace the NSTableView datasource code with bindings here so we can actually delete all of this. We did reference the old images array right here so we'll just very quickly replace that with the results of the query. But now we'll go to Interface Builder again where we can set up the bindings.
Here we see the multitude of Cocoa bindings-aware controllers that you can use in Cocoa. There's Object Controllers and Tree Controllers and Dictionary Controllers and so forth. We want an Array Controller since our results property is an array, so here we've instantiated it and we bind it its contents array here, we find the Image Library Controller there and since we've made it as the MetaDataQuery and app property we can very easily access it with a key-path such as this query.results. And now we come over here to the table column and we see here we will want to bind its value to the Array Controller and its Arranged Objects Key.
So go ahead and save that and run. Now here you see, you know, it's exactly the same as it was before but that's a very good result, that's what we want. We have eliminated code instead of just some simple bindings in Interface Builder and this is working exactly as it did before, but there's a hidden benefit to this as well. Here we'll come to where we have these images stored and we'll drop some more images in here, and we'll come back to our application and we see the table view has been automatically updated with the contents of that directory.
Spotlight is watching for notification of file system changes, updates the NSMetaDataQuery results, the NSArray Controller then takes that and pushes that up to the table view all dynamically for us. Cocoa bindings and Spotlight are some great features we hope that you use them in your own applications. Back to Chris now.
[Chris Parker]
So an interesting thing there, there's a lot of similar API for table views and dealing with that but the Cocoa Bindings technology allows you to eliminate a lot of glue code that would exist between your controller and your model objects and you can basically stop writing some of that boilerplate code, it's actually a very nifty and very interesting way to be able to program Once you've got all of this stuff hooked up you'll really sometimes want to be able to integrate with the Mac.
Some developers write an iPhone app and you're going to write a companion app for the Macintosh and a lot of this applies both to just being able to find both iPhones and Macintoshes on the network. But Bonjour is a network service discovery protocol you heard Simon Patience talk about it a little bit at his keynote presentation yesterday. You publish a service on the network and then you can find those services and the Foundation API for this is NSNetService known as NetService Browser.
It works the same way it does on the iPhone so if you've been discovering other iPhones via Bonjour on Wi-Fi you'll be able to discover other desktop computers as well in the same network so a lot of the same code that you're using there will come across as well. Keyed archiving a very simple way to be able to exchange data between the desktop and the iPhone is the Keyed Archiver and using the Keyed Archiver.
It's compatible between the phone and the desktop Macintosh so that the data that you create the NSData that come out of the Archiver you can just shoot right down to the phone and it'll unpack on the phone. You can customize the objects that come in and out of it so if your model is a little bit different in both places you'll be able to tweak that. The nice thing about this is that it's a very simple straightforward way to be able to exchange smaller amounts of data. Another higher level thing is Core Data.
If you've only been doing iPhone programming Core Data's a very interesting technology. It's basically an object persistence management framework. Right, so you've got a whole bunch of objects they have relationships to each other and you add something to an array or you remove something from an array those represent relationships between objects and this manages the entire object graph and the persistence of that object graph and it does just a fantastic job of dealing with high volumes of objects, hundreds of thousands of objects at a time.
It includes versioning of datastore so as you make your data-set richer and you enhance objects with attributes you'll be able to migrate that information forward and it takes the same predicate format, basically, you saw with NSMetaDataQuery and it uses those predicates to do searches on the database and it abstracts a lot of the SQL Light object relationship management stuff that you've been doing by hand directly against the database and it puts it right into a framework for you.
There are 2 talks about that this year. Introduction to Core Data on the iPhone and if you haven't used Core Data before this would be a fantastic talk for you to go to and that's in the Marina, Tuesday at 5. And What's New in Core Data talks about what's new in Core Data for both the phone and the desktop and that's in North Beach Wednesday at 10:30.
So once you've got all of this stuff together you're not done yet there are a lot of things you can do to be a really fantastic Macintosh application. There are many, many frameworks that enhance the user's experience in working with your documents and working with the computer. One of them is Spotlight, so we were a client of Spotlight in Kevin's demo to be able to search the hard drives that are connected to the machine in order to determine what files meet the criteria for the search.
Your documents can participate in Spotlight as well, so in a document-based application you'd write a Spotlight plug-in that indexes your content and adds that to the Spotlight database so when the user types something in and your app is installed and its index document's written by your app the user will see those documents appear in the search results, so you can participate in Spotlight as well.
Similarly, Quick Look, on your computers right now if you hit the Spacebar on a JPG or and image or a Keynote doc and anything like that that HUD window pops up and it has a preview of the document and in that preview you can interact with you can scroll through a PDF, you can look at multiple images, you can scroll through a presentation or look at all the sheets in a numbers document.
This is the Quick Look technology you would write a plug-in for that and actually participate in the same thing so when the user hits the Spacebar when one of your documents is selected in the Finder you get the same preview. Boy that just looks like the Eye of Sauron that's just going to bore you to death with his vacation pictures, doesn't it? Here we were running through Mordor, right? AppleScript, AppleScript is an absolutely incredible technology on Mac OS X and it has some really interesting characteristics.
You can publish your Object Graph and commands that you can do to your Object Graph, your model basically as an AppleScript Dictionary, nsDef and that allows the user to write scripts against your app, you know, create new window in front and add text to that window or add an object to that window or an image, things like that, and this allows you also to participate with Automator and a number of other technologies to have your application participate in workflows that your users write.
So even applications that never knew anything about each other you can actually string together workflows using AppleScript and it's a great boon to your users and if you participate in AppleScript it's another big bonus for your Macintosh application. Another big, big technology that we like to make a big deal of, Mac OS X has some of the best accessibility behaviors in an operating system so for users that are visually impaired or who have other difficulties in using a machine the accessibility frameworks allow you to annotate your application with everything that those users would need to be able to effectively use the computer even if they can't see the screen.
On Mac Os X this means being able to plug in an arbitrary Braille reader and have that give it all the information to the user right at their fingertips. So accessibility is another thing that if you're going to be writing a Mac application or even your iPhone applications now on iPhone 3.0 you really shouldn't ignore because this is a big, big feature that -- the kit does a lot for you free but you can also enhance it with that experience. So, please go out write your application, you know, use the model code that you already have or write a new application for the Macintosh using all of the skills that you already know in order to drive, you know, Xcode and all of those things.
So, Matt Drance is our iPhone and Mac Application Frameworks Evangelist. I got that wrong in my last talk so I had to say that very carefully to make sure that I got it right. His email address is [email protected] and there's a lot of documentation at the Mac Dev Center, [email protected]/mac.