0

I'm writing this in Swift. I have an NSTextField I've assigned a class in IB defined by:

class MyTextField : NSTextField, NSDraggingDestination {

I've overridden draggingEntered, draggingUpdated, prepareForDragOperation, performDragOperation in the subclass, but none of these is ever called and the system just puts stuff in the field as it sees fit. I want to handle the drag because, among other things, I don't want the default behavior of pasting a URL into the field if the user drags a file to it. Instead, if he does that, I want to get the display name of the file and use that instead.

What am I missing?

skytag
  • 3
  • 1
  • 4
  • After trying several approaches I solved this by subclassing NSTextView and returning my subclass in windowWillReturnFieldEditor. My subclass overrides performDragOperation, and in its implementation looks for a "public.file-url" string on the sender's pasteboard. If it finds one, it gets the item's display name and pastes it into the field, replacing any selected text. That's not ideal, as it doesn't insert it where the user releases the mouse, but it's much better than the default behavior. – skytag Nov 05 '14 at 10:54

1 Answers1

0

One of the responsibilities of any object implementing the <draggingDestination> protocol is to maintain an array of data-types which informs others what sort of data will trigger the methods you mention in your question. To allow your subclass to deal with drags from Finder or the desktop, I've found you need to register for three pasteboard types.

/* Sorry, not using Swift yet */

// MyNSTextField.m

- (void)awakeFromNib {
    [self registerForDraggedTypes:@[NSPasteboardTypeString,
                                    NSURLPboardType,
                                    NSFilenamesPboardType]];
}

At least on OS X 10.9, this is sufficient to fire your draggingEntered method. If all you want on the pasteboard is the filename, rather than the full URL or path, you need to (i) extract the name, (ii) clear the pasteboard and (iii) add just the name back onto the pasteboard:

- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender {

    NSDragOperation operation = NSDragOperationNone;

    NSPasteboard *pBoard = [sender draggingPasteboard];
    NSArray *array = [pBoard readObjectsForClasses:@[[NSURL class], [NSString class]]
                                           options:nil];

    if ([array count] > 0) {
        NSString *filename;
        if ([[array firstObject] isKindOfClass:[NSURL class]]) {
            // Possibly a file dragged from Finder
            NSURL *url = [array firstObject];
            filename = [[url pathComponents] lastObject];

        } else if ([[array firstObject] isKindOfClass:[NSString class]]) {
            // Possibly a file dragged from the desktop
            NSString *path = [array firstObject];
            BOOL isPath = [[NSFileManager defaultManager] fileExistsAtPath:path];
            if (isPath) {
                filename = [path lastPathComponent];
            }
        }

        if (filename) {
            [pBoard clearContents];
            [pBoard setData:[filename dataUsingEncoding:NSUTF8StringEncoding]
                    forType:NSPasteboardTypeString];
            operation = NSDragOperationGeneric;
        }
    }

    return operation;
}

On occasion the drag into the text field will happen so quickly that the above method is not triggered, in which case you're back to the same problem. One way around this is to implement the following text field delegate method:

// From NSTextFieldDelegate Protocol
- (void)textDidChange:(NSNotification *)notification

In this method you can compare the contents of your text field, with the contents of the pasteboard, if you're field now contains a valid system path and this path matches the contents of the pasteboard, you know you need to adjust the string in the text field. Fortunately, this seems to happen so quickly that it looks just like a normal paste operation.

Paul Patterson
  • 6,840
  • 3
  • 42
  • 56
  • Thanks, but that's not the problem. The problem is that by default if the user drags a file into an NSTextField the file's URL gets pasted into the field. This happens even if I only register the field or the underlying NSTextView for "public.plain-text." – skytag Nov 05 '14 at 10:48
  • You're right. In addition to specifying ``NSPasteboardTypeString`` you also need to specify ``NSURLPboardType``, and, for good measure, ``NSFilenamesPboardType`` - see updated answer. – Paul Patterson Nov 05 '14 at 14:07