Drag If You Want To
June 7th, 2006Handling 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.
June 7th, 2006 at 2:04 pm
I’d like to shove this text in the face of everyone who has poorly implemented (which means user unfriendly to me) d&d in their applications.
A fantastic – non Cocoa – example for your technique (or something even better I think) is iTunes’ batch change window. You can drag cover art to any position inside the window and it’ll just end up in the right place… good thinking of the programmers and very handy for the user.
Something I haven’t quite grokked yet in Cocoa drag and drop is the phenomenon you see in some Cocoa applications that text fields will only accept d&d while they have keyboard focus. Any idea about why and when that happens?
June 7th, 2006 at 2:15 pm
ssp: My theory about the “must be focused” problem is that it relates to the phenomenon of the field editor in Cocoa applications. Typically the text fields in a Cocoa UI are lightish-weight text controls that are not themselves capable of editing. When you click on one, a shared instance of NSTextView gets superimposed over the field while you edit. I suspect that NSTextField doesn’t support drag-n-drop out of the box, while NSTextView does.
I agree this is an awkward behavior – I hadn’t really thought of it until you mentioned it.
June 7th, 2006 at 10:26 pm
Can you change the focus to the text field as the cursor hovers over it, remembering the previous focused field after the drop is completed?
June 8th, 2006 at 1:22 am
Interesting explanation, Daniel.
I just tried this out once more to reproduce things. And in the place where it most frequently bugs me – in Mail – it looks downright ridiculous: Given a Mail window with the To, CC and subject fields and the keyboard focus on the To field, I can get three different drag & drop behaviours.
1. Drag to the To field: green + cursor appears, no other highlight in the field, text is inserted
2. Drag to the CC field: greeen + cursor appears, black frame appears around the field, text is inserted
3. Drag to the Subject field: no cursor change, no text insertion
Very very odd. I guess I should file a bug report on this.
I tried to make a film of that, but the cursor movements and cursor types don’t appear coorectly on there, so it’s not actually useful (http://www.stud.uni-goettingen.de/~s275288/stuff/DD.mov)
June 8th, 2006 at 6:28 am
Interesting bug case, ssp. This is all starting to sound familiar. I believe years ago I may have reported similar bugs at Apple. But you should report this again!
Here’s what’s going on with your Mail case. If you download Apple’s UI Element Inspector, you can see as you hover the mouse over the text fields in Mail, that all of them are NSTextField. But most of them are textfield enclosed in a scroll view. I’m supposing that somehow the presence of the scroll view is enabling the drag receptivity.
Stingerman: It seems like the bug could be solved by a category on NSTextField to cause it to accept text drags. But it must be more complicated than that. Maybe there are issues with sneaking text changes into the control without going through the whole “become focused, edit, then commit” process.
June 8th, 2006 at 7:47 am
Interesting observation about the scroll views. Still a bit odd.
I’ve filed the bug (#4578272) and we’ll see how that goes. It’s exactly the kind of problem that I don’t really expect to see improvements on.
(BTW, what exactly does Apple do to cripple their nib files in a way that IB refuses to open them? And how can I undo it? That just seems to make exploration or improvement of things unnecessarily difficult. Although using UI Element Inspector is a cool idea here!)
June 8th, 2006 at 7:36 pm
Curious about your last statement, ssp … which nib files are crippled? I was able to open a nib file from Mail.app with the Interface Builder included in Developer Tools 3.3. I’m running on an iMac core duo with OS X 10.4.6. Could you possibly have a permissions problem or preference file corruption?
June 9th, 2006 at 12:07 am
Err, so what kind of ‘permissions problem’ would that be where applications can read the files when running but I can’t.
I just checked anyway, and indeed in Mail I am able to open English nib files. I’m interested in the German ones, though which IB tells me ‘can’t be opened’ Try one of rhose…
June 9th, 2006 at 6:01 am
ssp: The problem you’re running into with editing the Nib files is that apparently the German (and other localization?) resources have been “pared down” such that they no longer contain the pertinent info for editing.
If you control-click a Nib file and select “Show Package Contents” you’ll see the constituent files that makes up a Nib package. It turns out that IB only really needs the Objects.nib file to instantiate the objects at runtime. The other two files are for Interface Builder’s benefit in showing the user how they may edit the relationships.
For some reason those files have been removed from the localization editions in Mail. If you drag the “Info.nib” and “Classes.nib” from an English counterpart Nib to inside a German one, you should then find yourself able to open and edit it. I’m pretty sure this is safe because the Info and Classes data don’t contain localized information. I might be wrong about that, though.
It might make a nice AppleScript or shell script that would simply go through and “repopulate” the localized versions, copying over from the English.
But anyway, back to your original question, it’s precisely the removal of these files that developers sometimes use as a way of “read-protecting” their Nib files.
June 9th, 2006 at 11:46 am
Thanks for that hint Daniel!
I’ve been frustrated by those ‘broken’ nib files for a while but never bothered to look into it. And I wondered why nobody else was complaining about it. It seems like the language issue here explains all of it.
Still sucks, though.
February 6th, 2007 at 7:04 am
This site has a solution to the text field dragging problem, using two field editors. http://homepage.mac.com/tom_zepko/cocoa/topics/drag-and-drop.html (Note: there appears to be a bug in your comment-posting code such that a return after a line of text causes all subsequent lines (in this case, the URL) to not be displayed (as seen in my entry just above). By going back a page in Safari and removing the carriage return, the URL appears. (This explanation almost got killed by the bug too, because I originally had added a carriage return between the URL and this text!)
February 6th, 2007 at 8:18 am
Southsez – hmm… thanks for the link and for letting me know about the comment entry bug. I’ll have to look into that!