Drag & drop (<NSDraggingDestination>) in Cocoa does not work - xcode

I want to implement some "simple" drag & drop into my Mac application.
I dragged in a view and entered "MyView" in my nib file accordingly.However I do not get any response in my console (I try to log messages whenever any of the protocol methods are triggered)
I have subclassed NSView like this:
#interface MyView : NSView <NSDraggingDestination>{
NSImageView* imageView;
}
and implement it like this:
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self registerForDraggedTypes:[NSImage imagePasteboardTypes]];
NSRect rect = NSMakeRect(150, 0, 400, 300);
imageView = [[NSImageView alloc] initWithFrame:rect];
[imageView setImageScaling:NSScaleNone];
[imageView setImage:[NSImage imageNamed:#"test.png"]];
[self addSubview:imageView];
}
return self;
}
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender{
NSLog(#"entered");
return NSDragOperationCopy;
}
- (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender{
return NSDragOperationCopy;
}
- (void)draggingExited:(id <NSDraggingInfo>)sender{
NSLog(#"exited");
}
- (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender{
NSLog(#"som");
return YES;
}
- (BOOL)performDragOperation:(id<NSDraggingInfo>)sender {
NSPasteboard *pboard = [sender draggingPasteboard];
if ( [[pboard types] containsObject:NSURLPboardType] ) {
NSURL *fileURL = [NSURL URLFromPasteboard:pboard];
NSLog(#"%#", fileURL);
}
return YES;
}
- (void)concludeDragOperation:(id <NSDraggingInfo>)sender{
NSLog(#"conclude sth");
}
- (void)draggingEnded:(id <NSDraggingInfo>)sender{
NSLog(#"ended");
}
- (BOOL)wantsPeriodicDraggingUpdates{
NSLog(#"wants updates");
}
- (void)updateDraggingItemsForDrag:(id <NSDraggingInfo>)sender NS_AVAILABLE_MAC(10_7){
}

If anyone still needs this, using:
[self registerForDraggedTypes:[NSArray arrayWithObjects:
NSFilenamesPboardType, nil]];
instead of:
[self registerForDraggedTypes:
[NSImage imagePasteboardTypes]]; // BAD: dropped image runs away, no draggingdestination protocol methods sent...
solved this problem for me. I suspect this works because my XIB is targeting osX 10.5. NSFilenamesPboardType represents a 10.5-and-previous 'standard data' UTI and [NSImage imagePasteboardTypes] returns 10.6-and-after standard data UTIs.
By the way, in - (BOOL)performDragOperation:(id NSDraggingInfo)sender, you can get a path for the file that was dropped with:
NSArray *files = [pboard propertyListForType:NSFilenamesPboardType];
Got the solution here:
http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/DragandDrop/Tasks/acceptingdrags.html#//apple_ref/doc/uid/20000993-BABHHIHC

Related

Drag Drop delegate for NSView could not set an attribute

I am using NSView delegate to read the dragged excel values. For this I have subclassed NSView. My code is like-
#interface SSDragDropView : NSView
{
NSString *textToDisplay;
}
#property(nonatomic,retain) NSString *textToDisplay; // setters/getters
#synthesize textToDisplay;// setters/getters
#implementation SSDragDropView
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender{
[self setNeedsDisplay: YES];
return NSDragOperationGeneric;
}
- (void)draggingExited:(id <NSDraggingInfo>)sender{
[self setNeedsDisplay: YES];
}
- (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender {
[self setNeedsDisplay: YES];
return YES;
}
- (BOOL)performDragOperation:(id < NSDraggingInfo >)sender {
NSArray *draggedFilenames = [[sender draggingPasteboard] propertyListForType:NSFilenamesPboardType];
if ([[[draggedFilenames objectAtIndex:0] pathExtension] isEqual:#"xls"]){
return YES;
} else {
return NO;
}
}
- (void)concludeDragOperation:(id <NSDraggingInfo>)sender{
NSArray *draggedFilenames = [[sender draggingPasteboard] propertyListForType:NSFilenamesPboardType];
NSURL *url = [NSURL fileURLWithPath:[draggedFilenames objectAtIndex:0]];
NSString *textDataFile = [NSString stringWithContentsOfURL:url usedEncoding:nil error:nil]; //This text is the original excel text and its getting displayed.
[self setTextToDisplay:textDataFile];
}
I am setting the textDataFile value to a string attribute of that class. Now I am using SSDragDropView attribute value in some other class like-
SSDragDropView *dragView = [SSDragDropView new];
NSLog(#"DragView Value is %#",[dragView textToDisplay]);
But I am getting null each time. Is it like I can not set an attribute value in those delegate methods?
The above problem can be resolved just by declaring a global variable in your SSDragDropView.h class.
#import <Cocoa/Cocoa.h>
NSString *myTextToDisplay;
#interface SSDragDropView : NSView
{
The same can be set inside the desired delegate method
- (void)concludeDragOperation:(id <NSDraggingInfo>)sender {
// .... //Your Code
NSString *textDataFile = [NSString stringWithContentsOfURL:url usedEncoding:nil error:nil];
myTextToDisplay = textDataFile;
// .... //Your Code
}
:)
Add
[dragView registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, nil]];
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender{
NSPasteboard *pboard = [sender draggingPasteboard];
NSArray *paths = [pboard propertyListForType:NSFilenamesPboardType];
NSLog(#"%#",paths);
[self setNeedsDisplay: YES];
return NSDragOperationGeneric;
}
Below code will print nil because you are not dragging anything on NSView.
SSDragDropView *dragView = [SSDragDropView new];
NSLog(#"DragView Value is %#",[dragView textToDisplay]);

drag and drop: NSView thinks source is an image that it isn't

I have two views.
1. One view has a grid of NSImageViews with images.
2. The other view has a grid of potential places to place the dragged image.
I think I have dragging working. If I click and drag an image from view 1, it works fine, but when I try to place it, the path of the source image is for an image on my desktop that isn't even part of my project. The result of the below code is that carriedData = nil, but I did some testing with NSLog and saw that it is sending the path for an image outside the project that is no my desktop.
Here is some of the D&D protocol methods I have implemented. myDragState is just en enumeration I use for highlighting the grid space that the drag is currently hovering over.
in Init...
[self registerForDraggedTypes:[NSImage imagePasteboardTypes]];
myDragState = dragStateNone;
Then the protocol methods
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
{
if ((NSDragOperationGeneric & [sender draggingSourceOperationMask]) == NSDragOperationGeneric)
{
myDragState = dragStateOver;
[self setNeedsDisplay];
return NSDragOperationGeneric;
}
else
{
return NSDragOperationNone;
}
}
- (void)draggingExited:(id <NSDraggingInfo>)sender
{
//turn off focus ring
myDragState = dragStateExited;
[self setNeedsDisplay];
}
- (void)draggingEnded:(id <NSDraggingInfo>)sender { }
- (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
{
return YES;
}
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
{
NSPasteboard *paste = [sender draggingPasteboard];
//gets the dragging-specific pasteboard from the sender
NSArray *types = [NSArray arrayWithObjects:NSTIFFPboardType, nil];
//a list of types that we can accept
NSString *desiredType = [paste availableTypeFromArray:types];
NSData *carriedData = [paste dataForType:desiredType];
if (nil == carriedData)
{
//the operation failed for some reason
NSRunAlertPanel(#"Paste Error", #"Sorry, but the past operation failed", nil, nil, nil);
myDragState = dragStateNone;
[self setNeedsDisplay];
return NO;
}
else
{
//the pasteboard was able to give us some meaningful data
if ([desiredType isEqualToString:NSTIFFPboardType])
{
NSLog(#"TIFF");
//we have TIFF bitmap data in the NSData object
NSImage *newImage = [[NSImage alloc] initWithData:carriedData];
[self setImage:newImage];
myDragState = dragStateSet;
}
else
{
//this can't happen
//NSAssert(NO, #"This can't happen");
NSLog(#"Other type");
myDragState = dragStateNone;
[self setNeedsDisplay];
return NO;
}
}
//redraw us with the new image
return YES;
}
- (void)concludeDragOperation:(id <NSDraggingInfo>)sender
{
//re-draw the view with our new data
[self setNeedsDisplay:YES];
}
- (void)drawRect:(NSRect)dirtyRect
{
// Drawing code here.
NSRect ourBounds = [self bounds];
if (myDragState == dragStateSet)
{
NSImage *image = [self image];
[super drawRect:dirtyRect];
[image compositeToPoint:(ourBounds.origin) operation:NSCompositeSourceOver];
}
else if (myDragState == dragStateOver)
{
[[NSColor colorWithDeviceRed:1.0f green:1.0f blue:1.0f alpha:0.4f] set];
[NSBezierPath fillRect:ourBounds];
}
else {
//draw nothing
}
}
Edit: So I figured this out. The problem was actually with the source. I wasn't copying it to the PBoard properly. My code for that is:
NSPasteboard *zPasteBoard = [NSPasteboard pasteboardWithName:NSDragPboard];
[zPasteBoard declareTypes:[NSArray arrayWithObject:NSTIFFPboardType] owner:self];
[zPasteBoard setData:[tileImageView.image TIFFRepresentation] forType:NSTIFFPboardType];
Now I am getting some weird effect when the images show though. As the images are placed in the destination imageview, they are being resized, but the larger draggable version is also still showing up. So, I am getting two images on the image view.

How to call registerForDraggedTypes in NSViewController?

I have a MainViewController with a splitview in it. Next I have two viewcontroller to control each of the views in the splitview. I want to be able to drag and drop a file in one of those views.
But I can't seem to get the dragging to work? There's no "plus-sign" on the file when dragged to the view and dropping it doesn't do anything either.
What am I doing wrong?
First here's MainViewController.m
fileViewController = [[FileViewController alloc] initWithNibName:#"FileViewController" bundle:nil];
terminalViewController = [[TerminalViewController alloc] initWithNibName:#"TerminalViewController" bundle:nil];
[splitView replaceSubview:[[splitView subviews] objectAtIndex:0] with:[fileViewController view]];
[splitView replaceSubview:[[splitView subviews] objectAtIndex:1] with:[terminalViewController view]];
Next, my code to handle the dragging in the FileViewController
#dynamic isHighlighted;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
NSLog(#"registering");
[self.view registerForDraggedTypes:[NSArray arrayWithObjects: NSFilenamesPboardType, nil]];
}
return self;
}
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender {
NSLog(#"[%# %#]", NSStringFromClass([self class]), NSStringFromSelector(_cmd));
NSPasteboard *pboard = [sender draggingPasteboard];
if ([[pboard types] containsObject:NSFilenamesPboardType]) {
NSArray *paths = [pboard propertyListForType:NSFilenamesPboardType];
for (NSString *path in paths) {
NSError *error = nil;
NSString *utiType = [[NSWorkspace sharedWorkspace]
typeOfFile:path error:&error];
if (![[NSWorkspace sharedWorkspace]
type:utiType conformsToType:(id)kUTTypeFolder]) {
[self setHighlighted:NO];
return NSDragOperationNone;
}
}
}
[self setHighlighted:YES];
return NSDragOperationEvery;
}
- (void)draggingExited:(id <NSDraggingInfo>)sender {
[self setHighlighted:NO];
}
- (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender {
return YES;
}
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender {
[self setHighlighted:NO];
return YES;
}
- (void)concludeDragOperation:(id )sender {
[self.view setNeedsDisplay:YES];
} // end concludeDragOperation
- (BOOL)isHighlighted {
return isHighlighted;
}
- (void)setHighlighted:(BOOL)value {
isHighlighted = value;
[self.view setNeedsDisplay:YES];
}
- (void)drawRect:(NSRect)frame {
[self.view drawRect:frame];
if (isHighlighted) {
[NSBezierPath setDefaultLineWidth:6.0];
[[NSColor keyboardFocusIndicatorColor] set];
[NSBezierPath strokeRect:frame];
}
}
I didn't seem to fix it, and because no one gave me any answers, I fixed it by making an NSView subclass and adding it to my viewcontroller like this.
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
draggable = [[DragView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
[[self view] addSubview:draggable];
}
return self;
}
I've never done OS X programming, but the problem is that since you're registering self.view to receive drag and drop, your self.view should handle the drag and drop operations and not your view controller. So you're correct in subclassing NSView to DragView, and you should pass the drag operations back to the view controller.
Also, I believe - (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)sender should be implemented.

Custom NSView Drag Destination

I'm trying to create a simple NSView that will allow a folder from Finder to be dragged onto it. A folder path is the only thing I want the view to accept as a draggable item. I've been trying to follow the Apple documentation, but so far nothing's working. So far, I've just tried to get the view to work with any file type, but I can't even seem to do that. Here's what I have so far:
-(id) initWithFrame:(NSRect)frameRect
{
if (self = [super initWithFrame:frameRect])
{
NSLog(#"getting called");
[self registerForDraggedTypes:[NSArray arrayWithObjects:NSPasteboardTypeString,
NSPasteboardTypePDF,
NSPasteboardTypeTIFF,
NSPasteboardTypePNG,
NSPasteboardTypeRTF,
NSPasteboardTypeRTFD,
NSPasteboardTypeHTML,
NSPasteboardTypeTabularText,
NSPasteboardTypeFont,
NSPasteboardTypeRuler,
NSPasteboardTypeColor,
NSPasteboardTypeSound,
NSPasteboardTypeMultipleTextSelection,
NSPasteboardTypeFindPanelSearchOptions, nil]];
}
return self;
}
-(BOOL) prepareForDragOperation: (id<NSDraggingInfo>) sender
{
NSLog(#"preparing for drag");
return YES;
}
The initWithFrame: method is getting called, but when I try to drag into the view the prepareForDragOperation: method doesn't ever seem to get called. My questions:
What am I doing wrong? Why isn't prepareForDragOperation: ever getting called?
What do I need to do to get the drag operation to only support dragging folders?
Update
I updated my registerForDraggedTypes: method with every type I could find. It now looks like this:
[self registerForDraggedTypes:[NSArray arrayWithObjects:NSPasteboardTypeString,
NSPasteboardTypePDF,
NSPasteboardTypeTIFF,
NSPasteboardTypePNG,
NSPasteboardTypeRTF,
NSPasteboardTypeRTFD,
NSPasteboardTypeHTML,
NSPasteboardTypeTabularText,
NSPasteboardTypeFont,
NSPasteboardTypeRuler,
NSPasteboardTypeColor,
NSPasteboardTypeSound,
NSPasteboardTypeMultipleTextSelection,
NSPasteboardTypeFindPanelSearchOptions,
NSStringPboardType,
NSFilenamesPboardType,
NSPostScriptPboardType,
NSTIFFPboardType,
NSRTFPboardType,
NSTabularTextPboardType,
NSFontPboardType,
NSRulerPboardType,
NSFileContentsPboardType,
NSColorPboardType,
NSRTFDPboardType,
NSHTMLPboardType,
NSURLPboardType,
NSPDFPboardType,
NSVCardPboardType,
NSFilesPromisePboardType,
NSMultipleTextSelectionPboardType, nil]];
I've noticed that the prepareForDragOperation: method isn't getting called when I drag a folder into the view. Did I miss a step?
Here's a simple little drag & drop view meeting those criteria:
MDDragDropView.h:
#interface MDDragDropView : NSView {
BOOL isHighlighted;
}
#property (assign, setter=setHighlighted:) BOOL isHighlighted;
#end
MDDragDropView.m:
#implementation MDDragDropView
#dynamic isHighlighted;
- (void)awakeFromNib {
NSLog(#"[%# %#]", NSStringFromClass([self class]), NSStringFromSelector(_cmd));
[self registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, nil]];
}
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender {
NSLog(#"[%# %#]", NSStringFromClass([self class]), NSStringFromSelector(_cmd));
NSPasteboard *pboard = [sender draggingPasteboard];
if ([[pboard types] containsObject:NSFilenamesPboardType]) {
NSArray *paths = [pboard propertyListForType:NSFilenamesPboardType];
for (NSString *path in paths) {
NSError *error = nil;
NSString *utiType = [[NSWorkspace sharedWorkspace]
typeOfFile:path error:&error];
if (![[NSWorkspace sharedWorkspace]
type:utiType conformsToType:(id)kUTTypeFolder]) {
[self setHighlighted:NO];
return NSDragOperationNone;
}
}
}
[self setHighlighted:YES];
return NSDragOperationEvery;
}
And the rest of the methods:
- (void)draggingExited:(id <NSDraggingInfo>)sender {
[self setHighlighted:NO];
}
- (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender {
return YES;
}
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender {
[self setHighlighted:NO];
return YES;
}
- (BOOL)isHighlighted {
return isHighlighted;
}
- (void)setHighlighted:(BOOL)value {
isHighlighted = value;
[self setNeedsDisplay:YES];
}
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
if (isHighlighted) {
[NSBezierPath setDefaultLineWidth:6.0];
[[NSColor keyboardFocusIndicatorColor] set];
[NSBezierPath strokeRect:self.frame];
}
}
#end
The reason prepareForDragOperation: isn't being called is that the dragging destination sequence follows a precise set of steps, and if the earlier steps aren't implemented, or are implemented but return a "stop the drag operation" type of answer, the later methods are never reached. (In your case, it doesn't appear that you've implemented the draggingEntered: method, which would need to return something other than NSDragOperationNone to continue on in the sequence).
Before prepareForDragOperation: is sent, the view is first sent a series of dragging destination messages:
A single - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender.
Depending on the NSDragOperation mask returned from that method, the following will be called if it's implemented in your class:
Multiple - (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender.
Depending on the NSDragOperation mask returned from that method, then prepareForDragOperation: will be called.
I'm using NSURLPboardType to register for stuff being dropped from the Finder (when I drag a file or a folder to my application, it receives them as urls)
Try this. And if it works, it'll solve your second problem : just check if the URL is a folder to accept or reject the drop :
// if item is an NSURL * :
CFURLHasDirectoryPath((CFURLRef)item)
// returns true if item is the URL of a folder.

Drag and Drop folder view cocoa

I need to make a drag and drop view in cocoa that will accept folders. I know it will use things like NSView and probably registerForDraggedTypes: (which I still am not sure how to go about using). Does anyone know how to get this working?
Thanks in advance
Make a class called DragDropView that subclasses NSView and set the view in MainMenu.xib to be of this type (Select your view, go to Identity Inspecor and write DragDropView in Custom Class).
Write the code (see below) for DragDropView and Run it. An empty window should appear.
Drag some folders onto your window. You should get the paths of the folders written in your console. Something like.
2014-02-01 11:18:10.435 Start[41767:303] (
"/Users/bob/Desktop/Heathers Animations",
"/Users/bob/Desktop/bird.atlas"
)
// DragDropView.h
#import <Cocoa/Cocoa.h>
#interface DragDropView : NSView
#end
// DragDropView.m
#import "DragDropView.h"
#implementation DragDropView {
BOOL isHighlighted;
}
- (void)awakeFromNib {
[self registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, nil]];
}
- (BOOL)isHighlighted {
return isHighlighted;
}
- (void)setHighlighted:(BOOL)value {
isHighlighted = value;
[self setNeedsDisplay:YES];
}
- (void)drawRect:(NSRect)frame {
[super drawRect:frame];
if (isHighlighted) {
[NSBezierPath setDefaultLineWidth:6.0];
[[NSColor keyboardFocusIndicatorColor] set];
[NSBezierPath strokeRect:frame];
}
}
#pragma mark - Dragging
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender {
NSPasteboard *pboard = [sender draggingPasteboard];
if ([[pboard types] containsObject:NSFilenamesPboardType]) {
NSArray *paths = [pboard propertyListForType:NSFilenamesPboardType];
for (NSString *path in paths) {
NSError *error = nil;
NSString *utiType = [[NSWorkspace sharedWorkspace]
typeOfFile:path error:&error];
if (![[NSWorkspace sharedWorkspace]
type:utiType conformsToType:(id)kUTTypeFolder]) {
[self setHighlighted:NO];
return NSDragOperationNone;
}
}
}
[self setHighlighted:YES];
return NSDragOperationEvery;
}
- (void)draggingExited:(id <NSDraggingInfo>)sender {
[self setHighlighted:NO];
}
- (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender {
return YES;
}
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender {
[self setHighlighted:NO];
return YES;
}
- (void)concludeDragOperation:(id<NSDraggingInfo>)sender {
NSArray *files = [[sender draggingPasteboard] propertyListForType:NSFilenamesPboardType];
NSLog(#"%#", files);
}
#end
Most of what you need is in the drag and drop documentation, but what you need specifically is the NSFilenamesPboardType. It's an array if file paths.

Resources