17
votes

I'm having a really difficult time trying to get NSPasteboard to work, so forgive me for my lack of understanding.

All I want is a simple way to write and retrieve from the pasteboard a NSUInteger value that is only for my application. All I am doing is drag dropping an item within my application, so all I need in the pasteboard is the item's id.

I have tried looking into NSPasteboardItemDataProvider and NSPasteboardItem, and NSData, but they all have convoluted way of doing something so simple. Infact, I have no idea what is the correct way of doing something so simple - every sample around seems to deal with the string type, or something much more complicated. [NSMutable SetData] selector doesn't seem to exist either even though there are no warnings in the code.

If someone can point me in the right direction, I would be very grateful. :)

Edit: I am using [dragImage ...] to help me with my dragging operations.

2
Why are you using NSPasteboard for something like that? It's a pretty bad decision if you don7t want/need that data outside of the app. You should use properties, delegates, notifications or at least NSUSerDefaults.Filip Radelic
I'm still learning my way around Cocoa so I might not always choose the best approach. I am doing a drag drop operation to move one view (of several) from one area to another, so I assumed this was a good way to implement it. It also had the nice rollback images generally used for these kind of operations.AndyTang
@fichek: NSPasteboard is part of implementing drag-and-drop.Peter Hosey
@PeterHosey yeah, I noticed only later he was referring to Mac, sorry :)Filip Radelic

2 Answers

48
votes

There are two ways to use NSPasteboard.

The older way: Pin data to the board yourself

First, you must declare the types that you will put on the pasteboard. You also appoint an object that will “own” the pasteboard, meaning that this is the object putting stuff on the pasteboard.

The next step is to put data on the pasteboard. This step is optional.

“Optional?!”, you ask. Yes: If you don't put data on the pasteboard for any type you have declared, and the pasteboard subsequently needs that data (to paste/drop), then the pasteboard will ask you (the owner) for it. This is called promising that data, and it's good when that data is expensive to copy (large) or generate.

There are five ways to put things on the pasteboard (besides getting asked for them):

  • As a string. Only good for plain text. The pasteboard will handle converting it to various encodings as needed.
  • As a property list. Only good for property lists (and yes, this is enforced all the way down, so an array of images doesn't count), or things that you can convert to and from property lists. This can include your own objects, if you implement that in them.
  • As raw data. Good for existing data types, such as image types (PNG, JPEG, etc.) and A/V types (MPEG-4, etc.).
  • As the contents of a file identified by path. Only good if what you're dragging/copying is already a file.
  • As the contents of a file wrapper. If you're not already using file wrappers, you can safely ignore this.

The newer way: Pin objects to the board and let them turn themselves into data

The new hotness, introduced in Snow Leopard, is to make your objects themselves able to write themselves to a pasteboard. This does require that they know everything about themselves that you will want on the pasteboard, including identifiers.

You need to make your objects conform to both NSPasteboardWriting and NSPasteboardReading.

The writing protocol will look really familiar now that you know the older way. The pasteboard asks your object what types it would represent itself as, then asks it for a property-list object for each type. (The protocol also provides a way to promise types instead of having the data for them requested immediately.)

To copy objects that conform to NSPasteboardWriting to the pasteboard, send the pasteboard a clearContents message (required in the new way, optional in the old way), then writeObjects: passing an array of the objects you want to copy.

The reading protocol is, as you'd expect, the inverse. To paste, you send the pasteboard a readObjectsForClasses:options: message. The pasteboard asks each of those classes what types it would recognize, then (optionally) tries to instantiate one or more of them from what's on the pasteboard.

The downside of this, particularly where reuse identifiers are concerned, is that it can end up breaking the separation of your model layer from your controller layer. Your reading initializer will need to know what to do with an identifier if you want it to return the existing object that has that identifier. That means it needs to either talk to the controller (bad) or duplicate the controller's lookup code (worse).

I don't know of a good way to implement move drag-and-drop (including, but not limited to, reordering) with the new protocols without running into this problem. For copy drag-and-drop (including, but not limited to, cross-process), it's fine, since you don't need identifiers for that—just generate the data on one end and create the new copy from it on the other.

The upside of the newer way is that handling of multiple items is much more sane. In the older way, there's only one “item”—indeed, not really any concept of items at all—in multiple types. If you wanted to copy or drag multiple things, you made an array and copied that as a single property list for some type, then re-created/retrieved the multiple things from that single property list on the other end. The newer way explicitly supports one or more items; when copying multiple things, you simply pass them all to writeObjects:.

Your case: A single NSUInteger identifier

Box it in an NSNumber (which is a property list) and use it in the older way.

0
votes

To follow up on Peter Hosey's, answer, take a look at PlaylistViewController.swift of my GitHub Helium port. This module handles the drag-n-drop for custom objects and interactions with a WKWebView.

It's a working, work in progress featuring the new shiny pasteboard adoption.