Reputation: 41
I started using objective-c today in order to develop an app for OSX (mountain lion). I have a bunch of buttons that I would like to drag them into some other object, for instance a text field. I followed the tutorials on apple's dev site, but I wans't able to get the drag part working (the drop part works, for instance, I can drag a file from finder into a text file and show its path).
I started by creating a NSButton subclass:
@interface mp3OCDDraggableButton : NSButton
and implemented the methods as described in: https://developer.apple.com/library/mac/#samplecode/CocoaDragAndDrop/Introduction/Intro.html
but the thing doest move!
I put some log messages in mouseDown:, which I can see in, but not if I replace it by mouseDragged: - does this tells me anything?
Can anyone post a simple example with this functionality? I couldn't find anything that works :\
many thanks in advance!
This is the code I have so far for the draggable button. Pretty much the same as in the tutorial.
//myDraggableButton.h
@interface myDraggableButton : NSButton <NSDraggingSource, NSPasteboardItemDataProvider>
@end
and
//myDraggableButton.m
#import "myDraggableButton.h"
@implementation myDraggableButton
- (void)mouseDown:(NSEvent *)theEvent:(NSEvent*)event
{
NSLog(@"mouseDown");
NSPasteboardItem *pbItem = [NSPasteboardItem new];
[pbItem setDataProvider:self forTypes:[NSArray arrayWithObjects:NSPasteboardTypeString, nil]];
NSDraggingItem *dragItem = [[NSDraggingItem alloc] initWithPasteboardWriter:pbItem];
NSRect draggingRect = self.bounds;
[dragItem setDraggingFrame:draggingRect contents:[self image]];
NSDraggingSession *draggingSession = [self beginDraggingSessionWithItems:[NSArray arrayWithObject:dragItem] event:event source:self];
draggingSession.animatesToStartingPositionsOnCancelOrFail = YES;
draggingSession.draggingFormation = NSDraggingFormationNone;
}
- (NSDragOperation)draggingSession:(NSDraggingSession *)session sourceOperationMaskForDraggingContext:(NSDraggingContext)context
{
switch (context) {
case NSDraggingContextOutsideApplication:
return NSDragOperationCopy;
case NSDraggingContextWithinApplication:
default:
return NSDragOperationCopy;
break;
}
}
- (BOOL)acceptsFirstMouse:(NSEvent *)event
{
return YES;
}
- (void)pasteboard:(NSPasteboard *)sender item:(NSPasteboardItem *)item provideDataForType:(NSString *)type
{
if ( [type compare: NSPasteboardTypeTIFF] == NSOrderedSame ) {
[sender setData:[[self image] TIFFRepresentation] forType:NSPasteboardTypeTIFF];
} else if ( [type compare: NSPasteboardTypePDF] == NSOrderedSame ) {
[sender setData:[self dataWithPDFInsideRect:[self bounds]] forType:NSPasteboardTypePDF];
}
}
@end
Upvotes: 4
Views: 2547
Reputation: 7534
I apologize for the necromancy, but I stumbled across this question while trying to implement this myself and would like to share the answer because it might be useful to others.
This solution uses categories on NSActionCell
and NSControl
because I needed to be able to drag multiple control types, not just buttons. You can adapt this to your needs/classes.
I have commented out code relating to a workaround hack for an undesired fade animation when hiding/unhiding the controls. I fiddled with implicit animations and the like, but couldnt figure out a better way. The hack does work nicely, but I left the window implementation code out.
@implementation NSControl (DragControl)
- (NSDraggingSession*)beginDraggingSessionWithDraggingCell:(NSActionCell <NSDraggingSource> *)cell event:(NSEvent*) theEvent
{
NSImage* image = [self imageForCell:cell];
NSDraggingItem* di = [[NSDraggingItem alloc] initWithPasteboardWriter:image];
NSRect dragFrame = [self frameForCell:cell];
dragFrame.size = image.size;
[di setDraggingFrame:dragFrame contents:image];
NSArray* items = [NSArray arrayWithObject:di];
[self setHidden:YES];
return [self beginDraggingSessionWithItems:items event:theEvent source:cell];
}
- (NSRect)frameForCell:(NSCell*)cell
{
// override in multi-cell cubclasses!
return self.bounds;
}
- (NSImage*)imageForCell:(NSCell*)cell
{
return [self imageForCell:cell highlighted:[cell isHighlighted]];
}
- (NSImage*)imageForCell:(NSCell*)cell highlighted:(BOOL) highlight
{
// override in multicell cubclasses to just get an image of the dragged cell.
// for any single cell control we can just make sure that cell is the controls cell
if (cell == self.cell || cell == nil) { // nil signifies entire control
// basically a bitmap of the control
// NOTE: the cell is irrelevant when dealing with a single cell control
BOOL isHighlighted = [cell isHighlighted];
[cell setHighlighted:highlight];
NSRect cellFrame = [self frameForCell:cell];
// We COULD just draw the cell, to an NSImage, but button cells draw their content
// in a special way that would complicate that implementation (ex text alignment).
// subclasses that have multiple cells may wish to override this to only draw the cell
NSBitmapImageRep* rep = [self bitmapImageRepForCachingDisplayInRect:cellFrame];
NSImage* image = [[NSImage alloc] initWithSize:rep.size];
[self cacheDisplayInRect:cellFrame toBitmapImageRep:rep];
[image addRepresentation:rep];
// reset the original cell state
[cell setHighlighted:isHighlighted];
return image;
}
// cell doesnt belong to this control!
return nil;
}
#pragma mark NSDraggingDestination
// message forwarding doesnt work for NSDraggingDestination methods
// because NSView implements empty methods for the protocol
/*
- (NSDragOperation)draggingEntered:(id < NSDraggingInfo >)sender
{
return [self.cell draggingEntered:sender];
}
- (void)draggingExited:(id < NSDraggingInfo >)sender
{
[self.cell draggingExited:sender];
}
- (BOOL)prepareForDragOperation:(id < NSDraggingInfo >)sender
{
return [self.cell prepareForDragOperation:sender];
}
- (BOOL)performDragOperation:(id < NSDraggingInfo >)sender
{
return [self.cell performDragOperation:sender];
}
- (void)concludeDragOperation:(id < NSDraggingInfo >)sender
{
return [self.cell concludeDragOperation:sender];
}
*/
- (void)draggingEnded:(id < NSDraggingInfo >)sender
{
// implement whatever you want to do here.
[self setHidden:NO];
}
@end
static NSPoint _dragImageOffset;
@implementation NSActionCell (DragCell)
- (void)setControlView:(NSView *)view
{
// this is a bit of a hack, but the easiest way to make the control dragging work.
// force the control to accept image drags.
// the control will forward us the drag destination events via our DragControl category
[view registerForDraggedTypes:[NSImage imagePasteboardTypes]];
[super setControlView:view];
}
- (BOOL)trackMouse:(NSEvent *)theEvent inRect:(NSRect)cellFrame ofView:(NSView *)controlView untilMouseUp:(BOOL)untilMouseUp
{
BOOL result = NO;
NSPoint currentPoint = theEvent.locationInWindow;
BOOL done = NO;
BOOL trackContinously = [self startTrackingAt:currentPoint inView:controlView];
BOOL mouseIsUp = NO;
NSEvent *event = nil;
while (!done)
{
NSPoint lastPoint = currentPoint;
event = [NSApp nextEventMatchingMask:(NSLeftMouseUpMask|NSLeftMouseDraggedMask)
untilDate:[NSDate distantFuture]
inMode:NSEventTrackingRunLoopMode
dequeue:YES];
if (event)
{
currentPoint = event.locationInWindow;
// Send continueTracking.../stopTracking...
if (trackContinously)
{
if (![self continueTracking:lastPoint
at:currentPoint
inView:controlView])
{
done = YES;
[self stopTracking:lastPoint
at:currentPoint
inView:controlView
mouseIsUp:mouseIsUp];
}
if (self.isContinuous)
{
[NSApp sendAction:self.action
to:self.target
from:controlView];
}
}
mouseIsUp = (event.type == NSLeftMouseUp);
done = done || mouseIsUp;
if (untilMouseUp)
{
result = mouseIsUp;
} else {
// Check if the mouse left our cell rect
result = NSPointInRect([controlView
convertPoint:currentPoint
fromView:nil], cellFrame);
if (!result)
done = YES;
}
if (done && result && ![self isContinuous])
[NSApp sendAction:self.action
to:self.target
from:controlView];
else {
done = YES;
result = YES;
// this initiates the control drag event using NSDragging protocols
NSControl* cv = (NSControl*)self.controlView;
NSDraggingSession* session = [cv beginDraggingSessionWithDraggingCell:self
event:theEvent];
// _dragImageOffset = [cv convertPoint:[theEvent locationInWindow] fromView:nil];
// Note that you will get an ugly flash effect when the image returns if this is set to yes
// you can work around it by setting NO and faking the release by animating an NSWindowSubclass with the image as the content
// create the window in the drag ended method for NSDragOperationNone
// there is [probably a better and easier way around this behavior by playing with view animation properties.
session.animatesToStartingPositionsOnCancelOrFail = YES;
}
}
}
return result;
}
#pragma mark - NSDraggingSource Methods
- (NSDragOperation)draggingSession:(NSDraggingSession *)session sourceOperationMaskForDraggingContext:(NSDraggingContext)context
{
switch(context) {
case NSDraggingContextOutsideApplication:
return NSDragOperationNone;
break;
case NSDraggingContextWithinApplication:
default:
return NSDragOperationPrivate;
break;
}
}
/*
- (void)draggingSession:(NSDraggingSession *)session willBeginAtPoint:(NSPoint)screenPoint
{
DragAnimationWindow* dw = [DragAnimationWindow sharedAnimationWindow];
NSControl* cv = (NSControl*)self.controlView;
NSImage* image = [[NSImage alloc] initWithPasteboard:session.draggingPasteboard];
[dw setupDragAnimationWith:cv usingDragImage:image];
[image release];
NSRect frame = [cv frameForCell:self];
frame = [cv convertRect:frame toView:nil];
[dw setFrame:[cv.window convertRectToScreen:frame] display:NO];
}
*/
- (void)draggingSession:(NSDraggingSession *)session endedAtPoint:(NSPoint)screenPoint operation:(NSDragOperation)operation
{
/*
if (operation == NSDragOperationNone) {
DragAnimationWindow* dw = [DragAnimationWindow sharedAnimationWindow];
NSRect frame = dw.frame;
NSPoint start = screenPoint;
start.y += _dragImageOffset.y;
start.x -= _dragImageOffset.x;
[dw setFrameTopLeftPoint:start];
[dw animateToFrame:frame];
}*/
// now tell the control view the drag ended so it can do any cleanup it needs
// this is somewhat hackish
[self.controlView draggingEnded:nil];
}
@end
Upvotes: 3
Reputation: 17218
Could the problem be that you're calling -setDataProvider:forTypes:
with NSPasteboardTypeString
but your -pasteboard:item:provideDataForType:
does nothing when passed that type?
Upvotes: 2