15

I'm trying to write an application that allows the user to drag files from the Finder and drop them onto an NSStatusItem. So far, I've created a custom view that implements the drag and drop interface. When I add this view as a subview of an NSWindow it all works correctly -- the mouse cursor gives appropriate feedback, and when dropped my code gets executed.

However, when I use the same view as an NSStatusItem's view it doesn't behave correctly. The mouse cursor gives appropriate feedback indicating that the file can be dropped, but when I drop the file my drop code never gets executed.

Is there something special I need to do to enable drag and drop with an NSStatusItem?

pablasso
  • 2,479
  • 2
  • 26
  • 32
Bryan Kyle
  • 13,361
  • 4
  • 40
  • 45

2 Answers2

30

I finally got around to testing this and it works perfectly, so there's definitely something wrong with your code.

Here's a custom view that allows dragging:

@implementation DragStatusView

- (id)initWithFrame:(NSRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        //register for drags
        [self registerForDraggedTypes:[NSArray arrayWithObjects: NSFilenamesPboardType, nil]];
    }

    return self;
}

- (void)drawRect:(NSRect)dirtyRect
{
    //the status item will just be a yellow rectangle
    [[NSColor yellowColor] set];
    NSRectFill([self bounds]);
}

//we want to copy the files
- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender
{
    return NSDragOperationCopy;
}

//perform the drag and log the files that are dropped
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender 
{
    NSPasteboard *pboard;
    NSDragOperation sourceDragMask;

    sourceDragMask = [sender draggingSourceOperationMask];
    pboard = [sender draggingPasteboard];

    if ( [[pboard types] containsObject:NSFilenamesPboardType] ) {
        NSArray *files = [pboard propertyListForType:NSFilenamesPboardType];

        NSLog(@"Files: %@",files);
    }
    return YES;
}


@end

Here's how you'd create the status item:

NSStatusItem* item = [[[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength] retain];

DragStatusView* dragView = [[DragStatusView alloc] initWithFrame:NSMakeRect(0, 0, 24, 24)];
[item setView:dragView];
[dragView release];
Rob Keniger
  • 45,830
  • 6
  • 101
  • 134
  • 3
    Awesome! But how I can handle click on this view and show menu? – Oleg Jun 20 '12 at 11:14
  • @Oleg were you able to implement click handling and displaying a menu on this view? – Shumais Ul Haq Feb 05 '13 at 10:10
  • 1
    I add button. And then add DragStatusView as subview. `_titleButton = [[NSButton alloc] initWithFrame:NSMakeRect(0, -2, 26, 24)]; [_titleButton setBordered:NO]; [_titleButton setButtonType:NSMomentaryChangeButton]; [_titleButton setImagePosition:NSImageOnly]; [_titleButton setBezelStyle:NSThickerSquareBezelStyle]; [_titleButton setTarget:self]; [_titleButton setImage:[NSImage imageNamed:@"IconDefault.png"]]; [_titleButton setAction:@selector(showMenu:)]; self.view = [[ILDragStatusView alloc] initWithFrame:NSMakeRect(0, 1, 26, 24)]; [self.view addSubview:_titleButton];` – Oleg Feb 06 '13 at 11:58
14

Since Yosemite, the method for setting a view on NSStatusItem is deprecated but fortunately there is a much nicer way using the new NSStatusItemButton property on NSStatusItem:

- (void)applicationDidFinishLaunching: (NSNotification *)notification {
    NSImage *icon = [NSImage imageNamed:@"iconName"];
    //This is the only way to be compatible to all ~30 menu styles (e.g. dark mode) available in Yosemite
    [normalImage setTemplate:YES];
    statusItem.button.image = normalImage;

    // register with an array of types you'd like to accept
    [statusItem.button.window registerForDraggedTypes:@[NSFilenamesPboardType]];
    statusItem.button.window.delegate = self;

}

- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender {
    return NSDragOperationCopy;
}

- (BOOL)performDragOperation:(id<NSDraggingInfo>)sender {
  //drag handling logic
}

Please be aware that the button property is only available starting in 10.10 and you might have to keep your old solution if you support 10.9 Mavericks or below.

Lukas Spieß
  • 2,478
  • 2
  • 19
  • 24
  • Thanks very much. For some reason, this doesn't work when I drag the file from the dock's "Downloads" stack (works fine from Finder). Any idea why that may be happening? – Coffee Bite May 07 '15 at 02:14
  • @CoffeeBite I'm running into the same issue, as reported in this radar: http://openradar.appspot.com/radar?id=1745403. Have you eventually found a way to solve this? – Pim Dec 18 '17 at 14:48
  • @Pim Yes. The `performDragOperation` isn't called, but `draggingEnded` is. So I check if the dragging ended within the frame of the status menu item. ``` func draggingEnded(sender: NSDraggingInfo?) { if sender != nil { if NSPointInRect(sender!.draggingLocation(), statusItem.button.frame) { if let pasteboard: NSPasteboard = sender!.draggingPasteboard() { //get file from clipboard data and do stuff. } } } } ``` – Coffee Bite Dec 18 '17 at 20:57