Drag If You Want To

Handling drags to your Cocoa application is pretty simple: you just have to implement the informal NSDraggingDestination protocol in some view hierarchy object that has been registered for particular drag types.

Usually a drag is handled by a specific view that can make sense of the drag’s data type. For instance, an NSTextView will accept text from the dragging clipboard and plop it down into the edited documnt. Most of the time, drags should only be handled by these specific views, but sometimes it makes sense to intercept “unhandled drags” and make something useful happen for the user. For instance, when a user drags a URL to a web browser, you might as well assume that they want to open the URL, even if they carelessly let go somewhere in the browser window’s non-descript background texture.

Apple made it easy to handle such “drags of last resort” at the window level, by kindly passing along all dragging offers from NSWindow to the window’s delegate. If your delegate simply implements the required methods, you can snag and make useful any drag that would otherwise bounce back to the originator.

But handling such non-specific drags at any level lower than NSWindow becomes tricky. There is no corresponding “delegation” when it comes to NSView, so drags that hover over or release in an uncooperative view will not be accepted, even if you’ve asked the view to register for the desired types.

When you are faced with the desire to “stake out” part of a window’s real-estate as a drag destination, the simplest way to do so is to sneak a custom NSView into the hierarchy that does accept drags. But instead of writing a custom view every time you need to do this, why don’t we follow AppKit’s lead and write our own NSView subclass that makes use of the delegation strategy.

RSDragDelegationView (MIT License) is a very simple NSView subclass that does just this. To mark the background area of a part of your UI as dragging-receptive, just select items in Interface Builder and “make subviews of custom view.” Set the class of the custom NSView to RSDragDelegationView (drag the header file to your IB document to teach it about the class), and connect the “mDraggingDelegate” outlet to the delegate instance. From your delegate code, you can now ask the custom view to register for your desired drag types, and all dragging messages will be forwarded on to you as they’re received.

An example of where I use this in my own code is in the “product registration pane.” I want to accept drags generously, because I advise users to drag their product registration email into the window after they’ve paid. If they can’t figure it out, they might email me, and while I’m always up for a nice customer chat, I’d just as soon talk about other things. There’s no reason for me not to make this as error-proof as possible, so I stake out the entire space of the tab view item in which the pane is installed. By using a custom drag-enabled view I avoid inflicting drag receptivity on the rest of the window, and keep the dragging-related code right near the controller code for the UI that needs it. If another section of the window’s UI needs to accept drags liberally, the same trick can be applied without complicating drag-handling at the window level.