2
votes

I'm trying to understand how best to impliment drag and drop of files from the Finder to a NSTableView which will subsequently list those files.

I've built a little test application as a proving ground.

Currently I have a single NSTableView with FileListController as it's datasourse. It's basically a NSMutableArray of File objects.

I'm trying to work out the best / right way to impliment the drag and drop code for the NSTableView.

My first approach was to subclass the NSTableView and impliment the required methods :

TableViewDropper.h

#import <Cocoa/Cocoa.h>

@interface TableViewDropper : NSTableView

@end

TableViewDropper.m

#import "TableViewDropper.h"

@implementation TableViewDropper {
    BOOL highlight;
}

- (id)initWithCoder:(NSCoder *)aDecoder {
    self = [super initWithCoder:aDecoder];
    if (self) {
        // Initialization code here.
        NSLog(@"init in initWithCoder in TableViewDropper.h");
        [self registerForDraggedTypes:@[NSFilenamesPboardType]];
    }
    return self;

}

- (BOOL)performDragOperation:(id < NSDraggingInfo >)sender {
    NSLog(@"performDragOperation in TableViewDropper.h");
    return YES;
}


- (BOOL)prepareForDragOperation:(id)sender {
    NSLog(@"prepareForDragOperation called in TableViewDropper.h");
    NSPasteboard *pboard = [sender draggingPasteboard];
    NSArray *filenames = [pboard propertyListForType:NSFilenamesPboardType];
    NSLog(@"%@",filenames);
    return YES;
}

- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
{
    highlight=YES;
    [self setNeedsDisplay: YES];
    NSLog(@"drag entered in TableViewDropper.h");

    return NSDragOperationCopy;
}

- (void)draggingExited:(id)sender
{
    highlight=NO; 
    [self setNeedsDisplay: YES];
    NSLog(@"drag exit in TableViewDropper.h");
}

-(void)drawRect:(NSRect)rect
{
    [super drawRect:rect];

    if ( highlight ) {
        //highlight by overlaying a gray border
        [[NSColor greenColor] set];
        [NSBezierPath setDefaultLineWidth: 18];
         [NSBezierPath strokeRect: rect];
    }
}

@end

The draggingEntered and draggingExited methods both get called but prepareForDragOperation and performDragOperation don't. I don't understand why not?

Next I thought I'll subclass the ClipView of the NSTableView instead. So using the same code as above and just chaging the class type in the header file to NSClipView I find that prepareForDragOperation and performDragOperation now work as expected, however the ClipView doesn't highlight.

If I subclass the NSScrollView then all the methods get called and the highlighting works but not as required. It's very thin and as expected round the entire NSTableView and not just the bit below the table header as I'd like.

So my question is what is the right thing to sublclass and what methods do I need so that when I peform a drag and drop from the Finder, the ClipView highlights properly and prepareForDragOperation and performDragOperation get called.

And also when performDragOperation is successful how can this method call a method within my FileListController telling it to create a new File object and adding it to the NSMutableArray?

1
Thanks @jsd I've seen that but it didn't help me. I was actually asking more of an architectural question. Which element to subclass etc.dwkns

1 Answers

5
votes

Answering my own question.

It seems that subclassing the NSTableView (not the NSScrollView or the NSClipView) is the right way to go.

Including this method in the subclass :

- (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender {
return [self draggingEntered:sender];
}

Solves the problem of prepareForDragOperation and performDragOperation not being called.

To allow you to call a method within a controller class, you make the delagate of your NSTextView to be the controller. In this case FileListController.

Then within performDragOperation in the NSTableView subclass you use something like :

NSPasteboard *pboard = [sender draggingPasteboard];
NSArray *filenames = [pboard propertyListForType:NSFilenamesPboardType];

id delegate = [self delegate];

if ([delegate respondsToSelector:@selector(doSomething:)]) {
        [delegate performSelector:@selector(doSomething:) 
                       withObject:filenames];
}

This will call the doSomething method in the controller object.

Updated example project code here.