Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cocoa NSTextField Drag & Drop Requires Subclass... Really?

Until today, I've never had occasion to use anything other than an NSWindow itself as an NSDraggingDestination. When using a window as a one-size-fits-all drag destination, the NSWindow will pass those messages on to its delegate, allowing you to handle drops without subclassing NSWindow.

The docs say:

Although NSDraggingDestination is declared as an informal protocol, the NSWindow and NSView subclasses you create to adopt the protocol need only implement those methods that are pertinent. (The NSWindow and NSView classes provide private implementations for all of the methods.) Either a window object or its delegate may implement these methods; however, the delegate’s implementation takes precedence if there are implementations in both places.

Today, I had a window with two NSTextFields on it, and I wanted them to have different drop behaviors, and I did not want to allow drops anywhere else in the window. The way I interpret the docs, it seems that I either have to subclass NSTextField, or make some giant spaghetti-conditional drop handlers on the window's delegate that hit-checks the draggingLocation against each view in order to select the different drop-area behaviors for each field.

The centralized NSWindow-delegate-based drop handler approach seems like it would be onerous in any case where you had more than a small handful of drop destination views. Likewise, the subclassing approach seems onerous regardless of the case, because now the drop handling code lives in a view class, so once you accept the drop you've got to come up with some way to marshal the dropped data back to the model. The bindings docs warn you off of trying to drive bindings by setting the UI value programmatically. So now you're stuck working your way back around that too.

So my question is: "Really!? Are those the only readily available options? Or am I missing something straightforward here?"

Thanks.

like image 234
ipmcc Avatar asked Jan 08 '11 22:01

ipmcc


1 Answers

After a bit more research it appears that "Yes, really, your two options are to either subclass NSTextField or use your NSWindowDelegate to handle drops." I'll go further and make the claim that the better way of the two, for garden variety cases of, "I want multiple drop zones in a single window" is to use the NSWindowDelegate method with hit checks, since you avoid the issue of having your drop-handling code on the view side. I ended up with this draggingUpdated: method on my window delegate class:

- (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)sender
{
    NSPasteboard *pboard = [sender draggingPasteboard];
    NSDragOperation sourceDragMask = [sender draggingSourceOperationMask];

    if ([pboard.types containsObject: NSFilenamesPboardType] && (sourceDragMask & NSDragOperationCopy))
    {
        NSView* hitView = [sender.draggingDestinationWindow.contentView hitTest: sender.draggingLocation];
        if (hitView && (hitView == mSourceTextField || hitView == mDestTextField))
        {
            return NSDragOperationCopy;            
        }
    }

    return NSDragOperationNone;
}

Obviously there's more to the whole picture, but this hitTest:-based approach has worked for me so far. I suspect that this would be slightly more complex if one were working with a multi-NSCell based control like an NSTableView or NSOutlineView, but unsurprisingly, those have their own drag handling methods.

Hope this helps someone else.

like image 167
ipmcc Avatar answered Sep 19 '22 18:09

ipmcc