Mac • 1:03:45
NSPasteboard provides the foundation for the application interoperability that all Mac users expect such as copy, paste, drag, and drop. Services harness the pasteboard, allowing users to integrate your app into their workflows across applications. Learn about significant enhancements to NSPasteboard and Services and how to leverage these capabilities in your app.
Speakers: James Dempsey, Peter Ammon
Unlisted on Apple Developer site
Downloads from Apple
Transcript
This transcript has potential transcription errors. We are working on an improved version.
Welcome to Pasteboards, Services, and Interoperability on Mac. My name is James Dempsey, I'm an engineer on the Cocoa Frameworks Team, and Application Interoperability on Mac OS X. Two of the heavy hitters of Application Interoperability are Pasteboards and Services. So that's what we're going to be focusing our attention this afternoon.
So, Pasteboards and Services. Pasteboards really provide the foundation for transferring data between applications on Mac OS X, and then Services build upon this foundation to allow an application to provide functionality that can be taken advantage of by any of the other applications on the system. Now, Pasteboards and Services aren't new.
They've been around since 10.0, and earlier, and some of you remember-- and, but another thing they have in common is that both Pasteboards and Services have had significant enhancements made in Snow Leopard. And that's what we're going to be talking about today. What's new with NSPasteboard and Services in Snow Leopard? We'll first start out by going through the new NSPasteboard APIs and how to use them, and then my colleague Peter Ammon will talk about how to provide and consume Services in Snow Leopard. All right.
Now this is marked as an expert talk. We're expecting that you have some familiarity with those topics, but even if you don't, I think that through the context of the presentation, you should be able to pick up a lot if you just stay seated. And with that, let's jump in and start talking about NSPasteboard item. So the Pasteboard really is the foundation for a lot of just fundamental user experience features on the Mac. So fundamental that we forget we're even using the feature anymore.
Things like copy and paste and drag and drop. And of course, System Services also rely on the Pasteboard. And really the purpose of NSPasteboard is to allow for a very simple way, pretty straightforward way of sharing data between applications or within an application. Now, before we jump in to the new Snow Leopard stuff, let's just do a very brief review of NSPasteboard and how it has behaved since 10.0 and since as far as we can tell the beginning of time.
So, NSPasteboard is kind of a cross-process dictionary-like structure. I kind of whimsically like to think of it as a magical dictionary that you get a hold of it in one process, you instantiate it by name, and then you can set data for various types and add different data representations. So it's dictionary-like because the data is keyed by its type.
And then in another process, you can ask for that same Pasteboard name, you get an NSPasteboard instance, and then the data representations put on over here are available in another process, and you retrieve them again by type, by getting the data for particular types. Now, one way that it's not dictionary-like is that the order of those types, those data representations is actually has some meaning.
In our example up here, we might have some rich text, some formatted text on the Pasteboard, and we're using the RTF format so that the richest representation of the data we're putting on the Pasteboard is always first. And then we might provide additional types, in this case, we're also providing some plain string data for an app link maybe terminal that doesn't deal in rich text.
So the first type is always the richest and then the following types are either less rich or perhaps a translation of the original type. So that's kind of the NSPasteboard in the nutshell as we've known and loved it for many years now. So what have we done in Snow Leopard? Well, probably the largest thing that we've done is we've added support for multiple Pasteboard items.
So as before in my whimsical mind, it was a magical dictionary, we now have a magical array of dictionaries. So each of those items is conceptually very similar to what we have thought off for years as the Pasteboard. So multiple items, each item can contain multiple data representations, and again, the order is still important. The second thing we've done is even though we've added new functionality to the Pasteboard, but also we've been looking at ways to make it easier to do common Pasteboard operations.
And so, we've shifted the focus from always dealing nitty-gritty of each individual type to be able to work more at the class level, and perhaps read and write things like NSColors and NSURLs directly on the Pasteboard. And then finally, we're taking the opportunity of these other changes to also move away from the pboard types and move to uniform type identifiers, which is a very rich typing system in Mac OS X, and actually, that will change those pboard types to UTIs on the Pasteboard.
So that's kind of a summary of the high level changes we've made to the Pasteboard in Snow Leopard. So-- but there's a lot of stuff in NSPasteboard that we really like, things we wanted to preserve. So briefly to mention those, although we can work at this class level, we still want to preserve this ability to get in there to the nitty-gritty and get and set every piece of data on every item.
NSPasteboard has always had these great convenience methods where instead of just setting and getting data for a particular type, you could get and set strings and property lists. And the Pasteboard would automatically serialize and deserialize for you. We've wanted to preserve those as well. And finally, the ability to provide data lazily, to promise to provide data at a later time and not necessarily do every expensive translation of types right upfront. So those things to preserve will be common threads as we look at the new API.
OK, that all said, let's look at the new way of doing things in NSPasteboard. Let's look at writing. So, in our example here, we have not 1, but 3 NSURLs and what we'd like to do is have 1 item show up on the Pasteboard for every NSURL. In Snow Leopard, we do something like this, we get a hold of a Pasteboard, of course it has what was previously on the Pasteboard, so clear the contents from what was previously there, and then we take that array of URLs and we simply tell the Pasteboard to write these objects. And when we do that, the Pasteboard will end up with 1 item per object that we handed it, and that item will have the appropriate types and data on it.
Now, how does the Pasteboard know what types of data are appropriate for a particular object? Well, NSURL implements a brand new protocol in Snow Leopard called NSPasteboardWriting. And this protocol is implemented by NSURL as well as many common Cocoa classes, and we'll see in a little bit, it's a public protocol so your objects can play along as well. So what does this operation look like in code? Something like this. So we have an array of URLs, and we get ahold of a Pasteboard that hasn't changed at all in Snow Leopard.
We clear the contents and then we write the objects. We hand in that array of objects that implement NSPasteboardWriting. And I don't know if we could have shortened it anymore than that. Now, I mentioned other Cocoa classes implement this as well, so we can write URLs to the Pasteboard, but if you now do the same with attributed strings for rich text data. Here, we're just putting one item on, so we're throwing it into an array before we write the objects, NSImage, NSColor, NSString, and NSSound. So for all of the common Cocoa classes that you're typically putting on the Pasteboard or reading off the Pasteboard, writing is very straightforward.
Now, how do your objects play along? Well, that's the NSPasterboardWriting protocol, 3 methods to implement, writable types for Pasteboard. In that method, you return an array of UTIs of the types your object is willing to provide to the Pasteboard. And again the order of that array is important, the richest type should be first.
Pasteboard property list for type is where you can ask for a type and you return the appropriate information that should be on the Pasteboard, but you'll notice its property list for type, not data for type. What's up with that? Well, if you give us back an NSData, if you return an NSData that's certainly a property list type of object, we're going to use that data as is.
However, if you return an NSString or any other property list object, it says if you had called setDataForType, or setPropertyListForType in the old API, and we'll automatically do the appropriate serialization to an NSData. And those are the only 2 methods that you have to implement, the only 2 that are required.
You can also specify some writing options for each type. And in Snow Leopard, there's only one writing option and it has to do with promising to provide a type.
So by default, that array of types that you hand back, we'll write the first type immediately, and we'll just promise to write the additional types. If you want to change that default behavior, implement writingOptionsForTypePasteboard and return the NSPasteboardWritingPromised option, which means in this type, we're not going to, the Pasteboard shouldn't grab it right away, it should promise to provide it later or no option at all, just zero.
Note that an object that is promising types and written to the Pasteboard is retained by the Pasteboard because the Pasteboard needs to fulfill that promise later potentially. So if you're passing an immutable object, you may want to copy it before writing it to the Pasteboard. And that sums up writing.
Let's talk about reading. So let's say we get a hold of this Pasteboard that we wrote to earlier. Now we want to get some NSURLs off of it. Well, we ask the Pasteboard, "Do you have any URLs?" We'll run through each item if it can construct the URL out of the types on that item, it will make one for you.
So in this case, the Pasteboard will give us back an array of 3 NSURLs. So what does that look like? Oh, and of course, we had NSPasteboardWriting, how does the Pasteboard know how to do this? NSPasteboardReading, of course, which is implemented by the same Cocoa classes that implement the writing protocol.
So in code, how does this work? Get hold of the Pasteboard, we create an array of classes that we're looking for, notice it can be more than one, and then we read objects for classes. There's some options we'll talk about in just a bit. And so, it's in a fairly straightforward way you can read multiple items at once. And in fact even if there are mixed types on the Pasteboard, you can read URLs, strings, images, attributed strings, all in one method call, if you just hand in a long enough list of classes as the array. So that's writing, and that's reading.
There's one other very common Pasteboard operation, which is validating the contents of the Pasteboard. So you may have a view and a drag comes in and you need to know is there anything interesting on the Pasteboard that my view is interested in, therefore I should validate and accept this drop. Or is there anything interesting on the Pasteboard, and therefore I should enable my Paste menu item.
So we also wanted to make validating, straightforward and easy, so there's one little change. Instead of reading the objects, we just ask if we can read object for classes. Again, get the Pasteboard, first 2 lines are exactly the same, specify some classes, and then ask if we can read. And again, with these 3 lines of code, you can potentially look for everything that your application is interested in without necessarily iterating through all the Pasteboard items to check in that sort of thing.
Now, we've had options arguments for the last 2 methods, it's the same set of options we can pass in for reading or validating whether we can read, let's talk about those now. It's an Options Dictionary, and they both deal with reading NSURLs from the Pasteboard. Now it's very common applications. Very often you may not be interested in WebURLs, they may only want local file URLs.
So in this Options Dictionary, you can say I'm looking for file URLs only, have an NSNumberBoolean and say, Yes. And in this example here, using this Options Dictionary to read URLs out of those 5 items with URL data, we end up with only the 3 NSURLs created that are file URLs. And then in addition to just looking for file URLs, another very common operation is to only want to accept, say, image files or text files. You're not only interested if there's a URL, but you want to know about the contents of that URL.
And so the second key is ContentsConformsToTypes-- is the key. The value you provide in the dictionary is an array of UTIs. In this case, we're looking for anything, any UTIs that NSImage supports. And in this case, when we do our read, out of those 5 items, only those that are file URLs that are also image files will be returned to us in NSURL.
Now I've done 2 reading examples, but this works equally as well with the canRead validation methods. And that sums up interacting with the Pasteboard for reading. But again, now, let's look at that NSPasteboardReading protocol that you can implement in your own custom classes and play along. Three methods: readableTypesForPasteboard, which is where you return the array of types that your applicator-- that that class is able to be created from.
That type-- that list is usually shorter than those that you're willing to provide. And then initWithPasteboardPropertyListType, again with the property list, not data, because you are able to specify how you want the data read off of the Pasteboard, and you do that by implementing the readingOptionsForTypePasteboard method. And there are 4 reading options in Snow Leopard. The first is ReadingAsData.
This is the default if you don't implement the optional reading options method, and the NSData on the Pasteboard gets handed into that init method as is. Then there's ReadingAsString where we will take the data and deserialize it to an NSString, and ReadingAsPropertyList, where we do the same with the property list. And so, this is similar to the property list for type or string for type methods in the existing NSPasteboard API. And there's one more reading option in Snow Leopard, because it's fairly common in custom model objects to implement the NSCoding so you can archive those objects.
And when folks do this, very often, they'll take that and they'll also use that keyed archive of their object as the Pasteboard type. And in this case, well, you already have written a perfectly good init method initWithCoder, so if you specify readingAsKeyedArchive, you don't even necessarily need to implement initWithPasteboardPropertyListType. You could just specify that what you're looking for is a keyed archive and we'll call initWithCoder instead automatically.
We'll just unarchive it from the Pasteboard for you. [Applause] I'll say it again. [Laughter] OK, excellent. So that really sums up, well, that really does sum up all of the reading and writing API in Snow Leopard pertaining to this class level operation. So that was 1, 2, 3, 4 methods for writing, reading, and validating, and then 3-- and 3 for reading and writing tips for your own classes to play along, so not a lot of new API, but a heck of a lot new functionality. But there is 1, this is going to hose me with that.
There is one piece that we did not talk about which is I said we wanted to preserve being able to access things type by type, item by item, how do we do that? It's not represented in the API we saw already. To do this, we've introduced a new class in Snow Leopard called NSPasteboardItem, and its interface is extraordinarily similar to the existing NSPasteboard single item API. And it gives you very fine-grain control over exactly what types are provided or promised, exactly what types you're reading, and it really serves as this generic wrapper object that could represent any item, any things that could be on the Pasteboard.
So what does that look like? If you're NSPasteboard aficionado like I am, I subscribe to "Pasteboard Aficionado Magazine," it looks exactly the same with one difference. We have set data provider for types instead of setting an owner for types. We're getting away from that owner technology or that owner terminology because data provider, I think, better specifies what's going on in this situation. Now, when will I use one of these as opposed to the API we saw previously? Well, sometimes, this mapping of a class to a list of types just isn't what you need in the application.
Your app may perhaps be dealing with CGImages which are not Objective-C objects, but you still want to pull tiff off the Pasteboard and create a CGImage or a pull PNG out of a CGImage and put it on to the Pasteboard, possibly multiple items worth. PasteboardItems are awesome for this sort of thing.
Another case is you might be a delegate or you might be subclassing a method where the object you're the delegate of are the superclasses that already done a bunch of work in the Pasteboard, and they hand you a Pasteboard that's already filled out with stuff.
In that case, you very often want to iterate through each item in the Pasteboard, look precisely at what types are there, and perhaps replace the data for particular types or a provider promise data for types of your own.
NSPasteboard item is extraordinarily handling that case as well. Let's look at a couple of code samples, they're a little longer. We get the Pasteboard. In this case, we're going to write some stuff to the Pasteboard, we create an NSPasteboardItem, and then we fill it full of stuff. In this case, we'll set some TIFF data, we'll promise some PDF data, we could actually put as many as we want into an array.
Once we have that array of Pasteboard items, we do what we've done many times already in this session, clear the contents and write the objects, and everything we filled out in those Pasteboard items, we get one item on the Pasteboard that's an exact match. Now the other case where we might be reading stuff from the Pasteboard, we get hold of a Pasteboard or maybe we get it from a delegate method that is called on us. We use a convenience method on NSPasteboard that's new in Snow Leopard to get the Pasteboard items. We iterate through them, and in this case, we're checking if the types on a particular item contain the TIFF Pasteboard type, TIFF data.
And if so, we're going to promise a PDF translation. Again, if you're familiar with NSPasteboards API, this is almost exactly the same. Now just a couple of notes about Pasteboard items, they're designed to be temporary objects. So don't hang on to it because once you write it to a Pasteboard, or you get one from a Pasteboard, that item is now bound to that Pasteboard. And in fact, it's also bound to that particular change count. So once somebody else clears the contents, you won't be reading anything out of that Pasteboard item.
So holding on to it won't do you much good, just use it in that method and let them go. Excellent. And then finally, the final big change in Snow Leopard is this transition to uniform type identifiers. Now, one thing to note is that the new Pasteboard APIs we've seen are UTI only So the existing APIs of NSPasteboard continue to work with pboard types and they also work with UTIs, so they accept both. But the new ones, if you handed a pboard type, you'll get a little log and nothing will happen.
But it would be pretty obvious something's gone awry. We've made moving the UTIs pretty straightforward. In most cases, it's a string for string replacement of constants. And another note is that we may be deprecating pboard types in a future release. So it's good to get a jumpstart on moving over.
Now let's talk about moving over this transition. So, all of the commonly used Pasteboard types have new constants in NSPasteboard.h, so string, RTF, TIFF, and the like. And then for maybe less commonly used Pasteboard types, we already have perfectly good constants defined in UTCoreTypes.h, the constant that defines that UTI type, and you would use those.
And really, the only exception to this switching one string constant for another is the filename's pboard type. In this case, it makes more sense since we've always been kind of putting multiple filenames into a single Pasteboard item, makes more sense to use the extra room to put one file per item.
And also, since we're moving away from using string pads for files to using file references in URLs, it makes sense to write file URLs to the Pasteboard and read them from the Pasteboard. Finally, well, if you take a look in NSPasteboard.h, details for each type are there, you just do a string for string mapping. Just a couple-- custom types. For your own types, if you don't share your type with anyone else, you just need to declare a new string that follows the UTI naming convention, kind of reverse DNS naming scheme.
And you don't need to formally declare the UTI in your Info.plist file. However, if you have a pboard type, an existing pboard type that third parties, some other applications depend on, or perhaps even a previous version of your application depends on, then you will want to declare a new UTI and formally declare it in your Info.plist and then associate it with your old pboard type. Now, the AppKit 10.6 release notes have examples of this, so does the new Pasteboard for 10.6 documentation. Two short types about the Pasteboard, more types are now available. The example here is TIFF. Used to be the only the bitmap type that the system would define.
Now there are dozens of UTIs. So if you're only looking for TIFF and somebody puts PNG on the Pasteboard, you're going to miss it. You should be asking for the broadest set of types that you can deal with. NSImage can help you with this, but again, the new API will take care of this eventually. And finally, say this with me 3 times fast. Peter put a PICT upon the Pasteboard.
Deprecated PICTS's a poor Pasteboard type to pick. [Laughter] [Applause] I'm only going to say it once because you know, we're time limited today. But the idea of this is, Please stop putting PICT on the Pasteboard. [Applause] It's deprecated. It was deprecated in Tiger along with QuickDraw, and it's completely unavailable, well, it relies on QuickDraw, and it's completely unavailable in 64 bit. Now, Cocoa and Carbon do a lot to make sure that older apps that are reading and writing PICT, usually 32-bit Carbon apps, are able to get translated PICT and that we're able to translate to a more modern type in your application. We've deprecated NSPICTPBoardType in Snow Leopard.
Hopefully, none of your apps actually will hit that deprecation, because you've been off of it for a while, but if you find some reason why you absolutely have to put PICT on to the Pasteboard or look for PICT, please file a bug, because I think we have all the cases. We really want to know why you still need PICT. And with that, I will turn it over. No? Call to action. Of course, we did all these. What should you do? First, move to UTIs.
In most cases again, string to string replacement, move away from file names in the FilenamesPboardType using multiple file URLs. The only thing is now, we can have multiple items on the Pasteboard, so people are going to start putting multiple items on the Pasteboard, which means that you should think about how your views and how your application should deal with multiple items. Now in some cases, like maybe in NSImageView, which shows one image, still just finding the first image and putting it on to-- using it might be appropriate.
But maybe in a drawing program if there are 3 images on the Pasteboard, you probably want to paste 3 discrete images into your drawing document. It's different for every app. So please give that some thought. And finally, just in general, adopt a new API. And with that, I will turn it over to my colleague, Peter Ammon, to talk about Services.
[ Applause ]
Thank you, James. So, the Services menu. We'll be doing an overview of what the Services menu is about. I'll tell you what's new about Services in Snow leopard, and then we'll do a sort of review of how you go about advertising this Service. So you get into the Services menu, how you provide a Service, so when the user chooses their Services item, how do you actually provide that Service.
How do you consume a Service so other Service providers can use-- I'm sorry. [Laughter] So that other Service providers can use the-- you can use Services from other applications, still what I meant to say. And finally, how do you go about debugging Services which isn't something we've had a good story for in the past, but at Snow Leopard there are some ways to debug them. And lastly, I'll show you really briefly, a new feature in Snow Leopard about making a Service for the workflow. So, an overview.
Now, I'm guessing most of you know what I'm talking about. But in case you don't, the Services menu is this menu right here, which is available in every application, and has been there since Mac OS X first shipped. So why do we have it? Well, if you have an application-like image capture that can take images from a camera, you don't want to make the user have to go to your application every time they want to have an image. You wanted to allow them to put the images in Mail for example or text edit, or pages or any application that can accept an image. So image capture is a Service provider.
It provides Services to, for example Mail. When that happens, we say Mail is a Service consumer. So even though this feature is provided by image capture, it appears in Mail, and the way this works is by passing data between Mail and image capture on a Pasteboard. So the result is that any application can extend the functionality of many others. And this isn't my saying.
This is in fact a line from the NeXTSTEP Developer documentation 14 years ago. [appleause] So something has changed since then, what's new in Snow Leopard? Well, there's no new methods and there's no new classes, but there are some changes in the way the Services menu looks and how you'd go about declaring the Service in your app's Info.plist.
[ Pause ]
My computer went to sleep. OK, so here I am in TextEdit, and I'm in a plain text view, not rich text, and by default, the Services menu is empty. [Applause] And that's it, we're done. No. [Laughter] In Leopard of course, there'll be a lot of Services there, but they would all be disabled.
And in Snow Leopard, we now hide Services that would otherwise be disabled in Leopard. So, well it's not entirely empty, right? There's also, there's one menu item. There are Services preferences, which are available all the time, and this will allow users to enable or disable Services that they want or don't want, and also to assign key equivalents to remove them.
[Applause] All right, so I'm going to switch to rich text, and I'll make this bigger. So now, again, there's nothing selected, but there's now, there's 2 items in the Services menu. There's Capture selection from screen, which is a grab Service, and Import image. And you'll notice that there's no menus. In Leopard, this would have all been within submenus, but in Snow Leopard, the Services menu is completely flat.
You'll also, all right. [Applause] Now if I select some text, we have 6 or 7 items now, 8 items, and you'll see that there's categorizations on the left. So you can have text and pictures, various categories, and of course, there's also icons that appear, but there's still some Services missing, for example, OpenURL or Reveal and Finder.
So I'm going to select the URL down here, and we see that OpenURL appears. So Services menu knows what URLs look like and can enable the Services that handle URLs to only appear when URL is selected. And it's not just web URLs. Here's an ancient gopher URL and it appears for that as well, so any URL type.
Another example is email addresses. So the Services send to will now know what email address look like and will appear there, and file paths as well. So Open, Reveal, Show Info only appears when there's a file path selected. And we also can recognize texts or scripts and languages.
So the Chinese text converted Services would appear all the time in Leopard. In Snow Leopard, they're still enabled by default, but they only show up if the text is written in Chinese. So here, this is simplified Chinese, and I can select it, and it will convert it to traditional.
So the last thing of note.... [applause] ...all right, the last important fact is that Services can also appear in the Context menu. We show a subset of Services, not all of them, but the ones that we think are most contextual, most relevant. So in this case, the Convert Selected Traditional Chinese Text will appear in the Context menu down at the bottom, so.
[Applause] And all these features, all these contextuality is fully available to any application and in fact, I'm writing an application, and I'll be showing, using this application to demo how you go about integrating Services programatically. So this is an application which maybe shows some pictures. It's got-- each picture has a name and the date it was taken. To represent that, we have a single class photo. Photo has the image, the URL to the image, and the name and date that the image was taken, and it implements the NSPasteboard writing protocol.
So the photo will write itself to the Pasteboard in a way that other applications can understand. So it would be nice if our application could provide Services to other apps. For example, if you could just select a date in Mail and say insert the photos taken on this date, and that photo would appear. So the first step to this is getting our menu items into the Services menu, and we call that advertising. So how do you advertise a Service? Well, you declare your Services in the app's Info.plist.
So here's an example of an application to provide a Service. A tool called pbs will scan and cache the Service declarations. A pbs existed in Leopard but it was totally private. In Snow Leopard, there are some developer features for pbs that I'll be showing you later. And at runtime, AppKit will read the data cache by pbs and use that to construct the Services menu. So here's an example of a Service, one of TextEdit Services that is declared in the app's Info.plist.
It starts of course with the NSServices declaration in the root level of the Info.plist, and below that is an array, so an app can provide more than one Service. It's an array of dictionaries. Most important key, or one of the most important keys is the NSMenuItem dictionary, and that's just the title of the menu item. It has to be within a subdictionary with the key by default because of the historical way we issue the localization.
So here, the title is Open Selected File. Another important key is NSMessage which is the name of the Objective-C method that your Service provider implements. So this is what connects the programatic Service providing to the app's Info.plist. And lastly, our NSSendTypes and NSReturnTypes, and these are Pasteboard types that are yet in Leopard and later UTIs, which are the types that your Service provides or receives.
So Open Selected File will open a text file by-- if you select the path, and in this case it receives text, right, text. But there's another key in Snow Leopard which is responsible for much of the contextuality, and this is the NSRequiredContext key. The Service only appears if the required context is satisfied. So in text edit, the Service should only appear if the text content contains a file path.
And this is the key to deciding whether the third party Services appear in the Services menu by default or not. You notice that the Services menu was devoid of all third party Services, and we made a decision to disable them all by default, but give you this other way to get back in as away of declaring, "Yes, I've updated my Service for Snow Leopard. It has a nice longer title to work with the not having-- the hierarchical menus.
It's got a nice icon. I'm ready for Snow Leopard." And even putting this key in your Info.plist, even if it's totally empty, will enable your Service to be available by default. Of course, if it's disabled by default because it's-- you wrote it for Leopard, the user can still enable it in preferences. So we saw that by default, Services that haven't replied to context generally won't appear.
But if you want to have, receive a file path, you can use the NSRequiredContext key, where was that, and we support not just file path, but you can also ask for URLs, addresses, date and email, and this is something in the list we'll be adding to later. We also saw that the Chinese text converter Services appear only when Chinese text is converted. You can request a specific script.
Here we're requesting simplified Chinese. For Services that appear in the finder, you can also request that my Service should only appear when a specific file type is selected. For example, the folder action setup Service would appear all the time in Leopard, even for text files or images. But in Snow Leopard, it appears only when a folder is selected, and the way this works is through requesting an NSSendFileTypes of public.folder.
Now you'll still receive URLs on the Pasteboard, though you won't try to receive folders on the Pasteboard but all the URLs will of course point to items that have these UTIs. You can also request that your Service only should appear in certain applications, in this case Xcode, or for certain word limit. For example, a stock ticker application might only provide a Service if exactly one word, you know, a 4-character stock ticker symbol is selected. So let's look at how we declare our Services on our Info.plist.
[ Pause ]
This one, yes. So our Services for our Photoviewer application have an NSMenuItem named getPhotosWithName. This corresponds to the Objective-C method get namedImages. It's going to accept Plain Text and will return an image, public.tiff, and is only going to appear if there is one word selected, because it will save, image can only be tagged by one word tags. And likewise, we'll get photos taken on date. This corresponds to the get datedImages method, and it will only appear if a date is selected.
So let's see if this works. [Murmuring] Here it is. So I'll select some text here and indeed getPhotosWithName appears like we expect it to. OK, well, great. You've got into the menu, and congratulations, the user selected your Service. What happens next?
So the TextEdit here is acting as a Service consumer. And if your application isn't already running, it's going to go ahead and launch your application. And it's going to look at the Send and ReceiveTypes of the Service that it's consuming. In this case, it's consuming text.
So it's gong to take the selected text, put it on a Pasteboard, there it goes, and it's going to move the Pasteboard all the way over to the Service provider, which will read off the text. Service provider will look for images or perform an operation on the text that it wants to. And if there is a ReturnType, in this case public.tiff, it will take that image, put it on the Pasteboard and send it back to the Service consumer. So your app will be launched if necessary. You don't have to ensure that it's already running.
And the way it receives the Service request is by registering its Service provider via the setServiceProvider method. And the provider receives a message depending on the Service. This is at NSMessage key and the Info.plist that I showed you earlier. So let's look at how we provide a Service in our Photoviewer app.
[ Pause ]
Get rid of that. So I'm moved them to the Info Category Service Provider. The first step is to register the Service provider saying, "NSApplication, I am the object that will receive the Service request." And this, you only have to do once. In this is, this happens at NSApplicationDidFinishLaunching. So now, we implement the methods that we said we would implement in our Info.plist, getNamedImages. We'll start by pulling out any string that was selected, NSPasteboardType String which is the UTI.
And as a courtesy to the user, we'll trim off any white space, and if we're able to find pullSelectedTextOut, we're going to find all the photos matching that text. So for every photo, if its name is equal to the name that we got on the Pasteboard, then we'll add it to an array. And if we were able to find any photos, then we'll call clearContents and then writeObject. So we write the photos, and because we implement the NSPasteboardWriting protocol, this is all we have to do.
And if we didn't find any photos, well, there is an NSError parameter here, and we can return a string saying, "No photo has that tag." Now, to getDatedImages, getting images all on-- that were taken on a specific date, it's almost exactly the same, except that we want to pull a date out of the Pasteboard. Now, even though we're looking for a date, we're getting an NSString on the Pasteboard because that's the type we requested. But in Snow Leopard, there's a new API, the NSTextChecking API, which we can use to convert the string to a date.
So we can use that to find the-- to scan the string and pull out any dates. So even though we requested a date, we still receive a string and it's our responsibility to find the dates within that string. So let's give these Services a try. I am in Mail and I'll say, take out this photo and I'll pick a photo-- I'll pick a photo and we can right-click, and I'll take out photos with name, and it works.
[ Applause ]
[ Pause ]
OK, so now we've gone ahead and we've implemented a Service. Oh, three things to keep in mind. If your Service doesn't have a ReturnType the process is asynchronous. So for example, OpenURL. If you select that from TextEdit, it's just going to pass the URL to Safari or your default web browser, and forget about it. But if it does have a return type, the calling app will actually sit and wait until your Service gets back to it.
Now, in Snow Leopard, users can actually cancel this process by hitting Escape or Command-period. But still, it's a good idea to show some feedback, a progress indicator or a dialogue indicating that you're performing the Service, if that Service might take some time to do. OK, consuming a Service, how do you use Services from other applications in your app? Well, now with Snow Leopard, Services are more useful than they've ever been.
So users are going to start to expect them to work in places where they didn't previously work. Anytime I can select text or select an image or select a file, I'm going to want to be able to use the Services corresponding to those types. Now your views can implement an API to support these Services, consume Services. And this isn't a new API.
This is the same API we've had since 10.0. But if you can use a standard control, either NSTextView or WebView, these implement the API for you. So then, in that case, you're already done. But if you have a custom control, for example, our photo app has a custom control that displayed photo. There are three steps you have to follow to implement the API. You have to register the types you support, indicate when you support those types, and then actually do the PasteboardWriting and Reading.
So I'll show you how we implemented this API in my app. So let's start by defining a convenient method, supported SendTypes. So these are the types that we can send to apps that provide Services, and we can send all imageTypes and we can also send FileURL and this old deprecated, all right, I don't know if it's deprecated, but this older NSFilenamesPboardType as well.
So the first step is indicating a type we support, and we call that-- we call the registered Services menu SendTypes, ReturnTypes method. And we pass the SendTypes we support, and we don't support any ReturnTypes, so we pass No for that. And this only has to be done once.
This is just a way of saying to AppKit,. "There's some sort of control in this application that can handle Services with these Send and ReturnTypes." So now, when the Services menu was actually shown, it's going to go through and say, "All right, is there a control right now that can handle the type for each of theses Services?" And will pass this to SendType and ReturnType corresponding to the types for these Services.
Now, if a Service doesn't have a Send or ReturnType, it's going to pass nil for that case. So because we don't support any return types, all we can do is send images and send FileURLs. If the return type is not nil, the Service expects to provide data back to us.
And in that case, we'll just call through to Super. But if the ReturnType is nil, we may be able to handle this Service. Now, if the SendType is nil, the Service doesn't want any input or output. So of course we could always handle it. But if the SendType is not nil, we need to make it one of the types we support, so we say supported SendTypes. If it contains this SendType, then we can support that.
But we're still not done because it may be that there's no file selected. All right, if Service operates an image, it should only work if there's an image actually selected. So if we have selected objects, at least one, well, then we can actually provide the Service ReturnSelf. Otherwise, we fall through to the Super implementation which is on NSResponder. And the last step is when the user actually chooses a Service that needs to interact with our control.
It calls write selection to Pasteboard. And this is a really easy method to implement for us. All we do is call clearContents and then write all of the selected objects, which are those photos, to the Pasteboard. And if we had the receiver data back again, we would implement read selection from Pasteboard to do essentially the same thing. Just clear the-- I'm sorry, to read the objects off the Pasteboard. So let me show you how that-- how this looks.
So I can select an image and in the Services menu we have SendFile. You can see it not only shows the Services that apply the files, but also Services that apply specifically to images. Because it knows that the URLs we're giving it back are images-- or point to images. And likewise, I can select via the context menu, we'll see that the most specific Services, here are the ones that apply specifically-- the image files also appear, set desktop picture. So that's all we have to do to consume a Service. That wasn't very bad.
The last step is debugging Services. It's just something I'm really glad we have. Because in Leopard, you would often write a Service and it just wouldn't show in the menu, and you don't know why. Maybe your app isn't registered with the Services architecture You have to log out and log back in, or maybe there's typo pull under Info.plist and it's not going to tell you. It's just not going to show up. Or maybe, there's another application with the same bundle identifier, an earlier version, and it's preferring that.
So how do you go about figuring out why your Service doesn't work? Well the answer to that is the pbs tool, which I mentioned earlier. The pbs is your window into the Services registration. And if you just run it with no argument, it will look for any changes to any Service anywhere on the system and it will immediately update the Services menu to show all the new Services or remove any that have gone away. You don't even have to re-launch the apps.
It will just do this for any apps that are running. But it will also typecheck your Info.plist. So if there's a typo somewhere, you have a dictionary instead of an array for example, it will tell you that and tell you exactly what went wrong. And lastly, you can pass it the dumpPboard option, and it will list all the Services that are registered.
So if you don't even know if it's found your app yet, you can call this and it will tell you the apps that are found. And an important note is that pbs is just for debugging. It may go away or change names later in the future so you shouldn't write any applications that depend on it or write on the installers to talk to pbs. Make sure only use it for debugging your app.
Now, with this, we can figure out if the Services architecture has found our application. But what happens at runtime if your Service doesn't appear? We saw all these crazy ways to make a Service contextual. How do we figure out why the Service is showing up or not showing up? Well, the NSDebugServices user default is your window into Services at runtime.
So for example, if I launch TextEdit with the NSDebugServices and I'm going to pass it the bundle ID of the application that I want to find out about the Service provider, it will output incredibly detailed information about why the Service is appearing or not appearing. So let me show you how I'd go about debugging Services with this application.
So I have a second application called the next version, this Buggy Picture Viewer, because it's buggy. The Info.plist here has Services called getPhotosTakenOnDateBuggy and getPhotosWithNameBuggy. So we can figure out whether we're looking at the new version or the old version. So if I open up TextEdit and I type some text and I go to the Services menu, it doesn't appear. All I see is the nonbuggy version.
So how do I figure out why is my Service not appearing? Well the first question is, has it even found the Service at all? So I'm going to call system-- I'm going to call pbs with the dumpPboard option, and it will tell me everything, every application that it's found. So I'll search for buggy. It's saying not found. It hasn't registered my Service. OK, let's call pbs without any options and have it just register all the Services it can find.
So I do that, and it's telling me, "Uh-oh, there's malformed Services entry in the Info.plist for the Service at this URL and that's my buggy app." So what could be wrong? Well, it tells me that as well. The value for key and NSWordLimit was supposed to of type NSNumber, but it was a string. So let's see if that's true. Here is that NSWordLimit, oh it is a string.
This should be integer. Let's change that into integer, and I'll rebuild, and I run pbs again. Oh, it didn't report any errors. Let's see if it actually registers our app. We'll dumpPboard, there it is, Buggy Picture Viewer. OK, let's actually try running the app. Let's try using it at-- from TextEdit here.
So I'll say Bridezilla, and [inaudible]. So I show this, and it's still not showing the Service. How do we figure out why not? I'll launch TextEdit again, but I'll specify NSDebugServices, and I'll give it the bundle ID of our buggy Service. And it's telling me, first off, that the buggy picture of your Service is enabled in the Services menu and the Contexts menu. So now I'll type some text again, and I'll show it. And it says that my Service was disqualified because it has a word limit of 3 of 1, but there were three words.
So that's pretty detailed. Let's get rid of two of these words. Oh, and there's the buggy Service. So we're able to use pbs and the NSDebugServices user default to figure out why our Service wasn't appearing and debug it. [Applause] We're really happy there's now a way to debug these Services. And there's one more feature I'd love to show you, which is using an automated workflow as a Service.
And this is really cool. So all I've done is launched Automator, and there's a new starting point Service. Thank you. OK, I've launched Automator, there's now a starting point called Service, and I'll choose that. And let's say I'm just a huge Python junky. I have to be able to run Python code from TextEdit and pages and everywhere that I can input text. Well, I can make a Service to do this with Automator really easily.
So let's start by making run shell script command, and I'll just pass the selected text straight to Python and I'll say that it replaces the selected text, and this should appear in any application, and I'll save it as runTextAsPython. OK, well, I'll type some-- this is a Python Easter Egg for those of you who know Python. And in the Services menu, well it appears right away, runTextAsPython and I select that, there it goes.
[ Applause ]
So it's really easy to make-- that only took, you know, a couple seconds just to make a workflow into a Service. And if this is something which seems interesting to you, [laughter] and I hope it does, now that we did this demo, I encourage you to go to the workflows as-- the Using Automator and Services to Integrate with Mac OS X session which is tomorrow, and also, you'll learn about how to provide your application as Automator actions. So users can create their own Services that are involved in your application.
So, what have we seen? Well, we see the NSPasteboard has all sorts of new APIs in Snow Leopard. Common operations like reading and writing from the Pasteboard usually require a lot less code. It's much simpler. And this is because we've shifted the focus away from pboard types to classes, and where we still use types, we move them from types, the old pboard types to UTIs.
We've also seen that Services have been rejuvenated in Snow Leopard. And the bar for a Service has been raised. So it's important that all Mac OS X apps that are relevant to Services integrate with Services as much as possible. Some ways to do that are to provide contextual Services from your application, via Service provider allowing Services from other apps to be consumed within your app. And lastly, to expose your app as Automator action so that users can construct Services from your app.
So for more information, you can contact our Evangelist, Matt Drance, and the Services Implementation Guide has been updated for Snow Leopard. If you search in Xcode documentation for the Services Implementation Guide, you'll see all those keys that I showed you before as well as the pbs debugging tricks. And the Pasteboard Programming Guide also is updated to show you some of the new Pasteboard APIs.
[ Laughter ]
[ Applause ]
[ Cheers ]
Thank you.
Thank you.
Thank you, Peter. Actually, I just have been really impressed with all the new Services stuff. Just [applause] put it together, that's awesome.
[ Applause ]
So, for the last number of years, some session has a little guitar icon and a little song shows up at the end.
And well, this year, well first, let me introduce who else is on stage with me. You may notice that Gordie Freedman who is the original Breakpoint guitarist wasn't able to make it this year. But we have John Skello [phonetic], our newest Breakpoints, and he plays guitar well enough for the both of us... [applause] ...and joining us. And then as always, on keyboard, the best slide advanced man in the business, Mr. Victor Alexander.
[Applause] And so this year, as you've seen, we've made a lot of changes to Pasteboards, lot of changes to Services, and kind of talked through a lot of new Pasteboard APIs. So I thought this would be a good opportunity to do a little song that did some review of what we've just heard. And also, since the Pasteboard is all about getting from point A to point B, I thought what better way to-- what better style for a song than a little Rock-a-Billy traveling music. So without any further ado, "Going To Need A Pasteboard".
[ Music ]
[ Singing ]
Well, you got an application, has some things to share with them other applications. Well you got to stick that stuff somewhere. You're going to need a Pasteboard, baby, yeah, to move that stuff around. Oh, yeah. Treat the Pasteboard right then well it will not let you down, Oh, no. Well, Pasteboard's quite inviting with the classes you invent. NSPasteboardWriting is the protocol you implement. So you can write them on the Pasteboard, baby, common Cocoa classes too.
You just toss them on the Pasteboard, and that data will shine through. Let's do some writing. Let's get a Pasteboard, then clear them contents, and write them up, yes, it's literally 1, 2, 3 to write them on a Pasteboard, baby, easy as falling off a log, oh yeah. But don't do the Pasteboard wrong, no or it might diss you in its blog. [Laughter] Oh, yeah. We'll now you're dragging and you're dropping, just the way you like to do, but that joyride will be stopping if you can't get the data through.
You're going to need a Pasteboard, baby, the contents of the drag, oh yeah. So treat the Pasteboard nice, yeah, and you won't hit a bump or snag, oh no. I say NSPasteboardReading has the power to create. It's what the Pasteboard's needing so it can instantiate what you're reading off the Pasteboard, baby, all that its contents will allow, oh yeah. It just answer what you're needing, you'll receive without much sweat upon your brow. Let's read. First get a Pasteboard, specify some classes, and then read them objects oh, so literally 1, 2, 3 to read them off a Pasteboard, baby.
Yeah, you can't read all night. But don't leave the Pasteboard lonely, no, no, 2 reads don't make a write. [Laughter] And then sometimes you just might find that you require more specificity to determine type by type exactly what you read and write is supposed to be. So then you use a Pasteboard item, you can read them, you can write them just as you desire. Use them to achieve your goal or if you're just into control, that should surely set your soul on fire. Well, you might want to write a Service to make any text or tweak. Then you might get nervous how that transfer was complete.
Well you're going to need a Pasteboard, baby, for the Service you provide, oh yeah. Well, just stick your data on there, Pasteboard takes it for a ride. Well, the Pasteboard ain't too flashy, no, but that don't make me sweat. It you ship an app without copy and paste, just see how far you get.
[Laughter] You're going to need that Pasteboard, baby. It surely would be missed, oh yeah. Oh, treat the Pasteboard right, now, I think your users will insist, oh yeah. Oh, treat the Pasteboard nice now, I think by now you get the gist, oh yeah. I say use the Pasteboard wisely, and build an app we can't resist, oh yeah.
[ Applause ]
1