Accepting drag operations in an NSCollectionView subclass - objective-c

I've subclassed NSCollectionView and I'm trying to receive dragged files from the Finder. I'm receiving draggingEntered: and returning an appropriate value, but I'm never receiving prepareForDragOperation: (nor any of the methods after that in the process). Is there something obvious I'm missing here?
Code:
- (void)awakeFromNib
{
[self registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, nil]];
}
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
{
NSLog(#"entered"); //Happens
NSPasteboard *pboard;
NSDragOperation sourceDragMask;
sourceDragMask = [sender draggingSourceOperationMask];
pboard = [sender draggingPasteboard];
if ([[pboard types] containsObject:NSFilenamesPboardType])
{
NSLog(#"copy"); //Happens
return NSDragOperationCopy;
}
return NSDragOperationNone;
}
- (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
{
NSLog(#"prepare"); //Never happens
return YES;
}

This is pretty late, but I found the problem:
NSCollectionView silently provides an incompatible implementation of:
-(NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)sender
...and Apple hasn't documented this. If you simply implement that method to re-invoke the draggingEntered method, everything works fine, e.g.:
-(NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)sender
{
return [self draggingEntered:sender];
}
(I came to SO hoping to find an explanation of what "magic" this custom implementation provides, since that too is ... undocumented (thanks, Apple!). I'm guessing it does something clever with managing an insertion-point within the CollectionView?).
UPDATE: it seems the special magic is inside the NSCollectionView's delegate object. For some reason, Xcode4 was claiming there was no delegate for me, but assigning it built and ran OK. Check out all the custom / semi-documented drag/drop methods there.
(or just do as I describe above and override the custom behaviour, and implement something that works and you can understand)

You might want to try these delegate methods from the NSCollectionViewDelegate Protocol
- (NSDragOperation)collectionView:(NSCollectionView *)collectionView validateDrop:(id <NSDraggingInfo> )draggingInfo proposedIndex:(NSInteger *)proposedDropIndex dropOperation:(NSCollectionViewDropOperation *)proposedDropOperation;
- (BOOL)collectionView:(NSCollectionView *)collectionView acceptDrop:(id <NSDraggingInfo> )draggingInfo index:(NSInteger)index dropOperation:(NSCollectionViewDropOperation)dropOperation;
- (BOOL)collectionView:(NSCollectionView *)collectionView canDragItemsAtIndexes:(NSIndexSet *)indexes withEvent:(NSEvent *)event;
- (NSImage *)collectionView:(NSCollectionView *)collectionView draggingImageForItemsAtIndexes:(NSIndexSet *)indexes withEvent:(NSEvent *)event offset:(NSPointPointer)dragImageOffset;
- (NSArray *)collectionView:(NSCollectionView *)collectionView namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropURL forDraggedItemsAtIndexes:(NSIndexSet *)indexes;
- (BOOL)collectionView:(NSCollectionView *)collectionView writeItemsAtIndexes:(NSIndexSet *)indexes toPasteboard:(NSPasteboard *)pasteboard;
The first two methods in particular.

I went through this a while ago. It seemed counterintuitive to me, but the only way I could get it to work was to set up the associated scroll view as the drop target.

Related

Cannot get drag and drop to work onto NSCollectionView

There is probably a simple mistake that I'm making, but I simply cannot get dropping of files onto an NSCollectionView to work even in the most basic way.
In a test project, I have an NSCollectionView on a window, and the view controller is both its delegate and data source. I want to be able to drag files from the Finder onto this collection view.
From reading the docs, all I should have to do is:
Register for dragged type(s):
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
NSLog(#"Registering dragged types for collection view: %#", self.collectionView);
[self.collectionView registerForDraggedTypes:#[NSFilenamesPboardType]];
[self.collectionView setDraggingSourceOperationMask:NSDragOperationEvery forLocal:YES];
[self.collectionView setDraggingSourceOperationMask:NSDragOperationEvery forLocal:NO];
}
And then implement these two methods:
-(NSDragOperation)collectionView:(NSCollectionView *)collectionView validateDrop:(id<NSDraggingInfo>)draggingInfo proposedIndex:(NSInteger *)proposedDropIndex dropOperation:(NSCollectionViewDropOperation *)proposedDropOperation {
NSLog(#"Validate drop: %#", draggingInfo);
return NSDragOperationMove;
}
-(BOOL)collectionView:(NSCollectionView *)collectionView acceptDrop:(id<NSDraggingInfo>)draggingInfo index:(NSInteger)index dropOperation:(NSCollectionViewDropOperation)dropOperation {
NSLog(#"Accept drop: %#", draggingInfo);
return YES;
}
But none of the two methods is ever called, when I try to drag an item onto the collection view, which makes me think that the registerForDraggedTypes: call is not working as expected.
What can be the issue here? What else do I have to look into?
From OS X 10.11 the NSCollectionViewDelegate methods take an index path instead of an index. For instance in
-(NSDragOperation)collectionView:(NSCollectionView *)collectionView validateDrop:(id<NSDraggingInfo>)draggingInfo proposedIndex:(NSInteger *)proposedDropIndex dropOperation:(NSCollectionViewDropOperation *)proposedDropOperation
the proposedIndex: parameter is replaced by proposedIndexPath:
- (NSDragOperation)collectionView:(NSCollectionView *)collectionView validateDrop:(id <NSDraggingInfo>)draggingInfo proposedIndexPath:(NSIndexPath * __nonnull * __nonnull)proposedDropIndexPath dropOperation:(NSCollectionViewDropOperation *)proposedDropOperation

How to properly use the webkit delegate to start and stop a spinner, OS X, OBJ-C

It seems like this should be so simple, but I am pretty new at Objective-C. What I want to do is simply start and stop a spinner while my WebView is loading. This is an OS X app. Everything I have searched for is for Cocoa Touch, I am using just Cocoa. In my AppDelegate.m I have to methods that start and stop the spinner (This does work, I tested it).
-(IBAction)goSpin:(id)sender
{
[spinner startAnimation:self];
}
-(IBAction)stopSpin:(id)sender
{
[spinner stopAnimation:self];
}
I also have the two delegate methods for webView, which I overrode.
-(void)webView:(WebView *)sender didStartProvisionalLoadForFrame:(WebFrame *)frame
{
[self goSpin:self];
}
-(void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame
{
[self stopSpin:self];
}
Basically, I would like to know how I get my webView to set it's delegate. Usually I have to do something in the .h file, but I can't find any references that list what the webKit delegate is that would work for this. Any help would be appreciated.
The
-(void)webView:(WebView *)sender didStartProvisionalLoadForFrame:(WebFrame *)frame
and
-(void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame
methods are part of the WebFrameLoadDelegate Protocol.
WebView has a frameLoadDelegate property. Set it on your WebView instance by calling [webView setFrameLoadDelegate:delegate], where delegate is an NSObject that implements the two methods (it will be easiest for you to make self the frameLoadDelegate here). Since WebFrameLoadDelegate is an informal protocol, delegate should declare the two methods in its .h file, rather than add <WebFrameLoadDelegate> to its class declaration as with a formal protocol.
You can override these webview delegate methods to start and stop a spinner.
- (void)webViewDidStartLoad:(UIWebView *)webView{
// In this method you have to start a spinner.
}
- (void)webViewDidFinishLoad:(UIWebView *)webView{
// In this method you have to stop the current spinner.
}
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error{
// In this method you have to stop the current spinner.
}

Drag&Drop is not enabled for a NSOutlineView although I've implemented delegate methods

I'm not able to enable drag and drop for a NSOutlineView. I've implemented the related method of the NSOutlineView Delegate.
But it seems that when I click an item, I can't even dragging it (I don't see animation).
- (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id < NSDraggingInfo >)info item:(id)item childIndex:(NSInteger)index
{
return YES;
}
- (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id < NSDraggingInfo >)info proposedItem:(id)item proposedChildIndex:(NSInteger)index
{
return NSDragOperationMove; //not sure about this one.
}
thanks
UPDATE:
I'm implementing forOSX >= 10.5
- (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard
{
NSString *pasteBoardType = [self pasteboardTypeForTableView:outlineView];
[pboard declareTypes:[NSArray arrayWithObject:pasteBoardType] owner:self];
NSData *rowData = [NSKeyedArchiver archivedDataWithRootObject:items];
[pboard setData:rowData forType:pasteBoardType];
return YES;
}
The methods you implemented are just for the destination of the drag. You still need to implement the dragging source methods. For whatever reason Apple's NSOutlineViewDataSource Protocol documentation is missing these methods but you have two options:
If you are building 10.7+ use Xcode's Open Quickly command to look in NSOutlineView.h and find the relevant methods. Also check out the DragNDropOutlineView sample app.
If you are supporting previous OS's then use NSTableView's delegate methods. See NSTableViewDataSource Protocol Reference. Remember that NSOutlineView is a subclass of NSTableView and can use the table view methods.
At a minimum you will probably want to implement outlineView:writeItems:toPasteboard:
/* Dragging Source Support - Optional for single-image dragging. This method is called after
it has been determined that a drag should begin, but before the drag has been started. To
refuse the drag, return NO. To start a drag, return YES and place the drag data onto the
pasteboard (data, owner, etc...). The drag image and other drag related information will
be set up and provided by the outline view once this call returns with YES. The items array
is the list of items that will be participating in the drag.
*/
- (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pasteboard;
Update:
If the item can be dragged but won't drop on anything then most likely outlineView:validateDrop:proposedItem:proposedChildIndex: is not being called. This would mean you haven't registered the pasteboard type which you do with registerForDraggedTypes:. You would do this somewhere in the view controller, probably in awakeFromNib.
[outlineView registerForDraggedTypes:[NSArray arrayWithObject:#"myPasteBoardType"]];
To move the item (and all its children) modify your model in outlineView:acceptDrop:item:childIndex:. Then send reloadData to the outlineView.
To make your outline view a dragging source, you must implement:
- (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pasteboard;
This should address what you described, but you've got a lot more work to do beyond this.

Cocoa WebView Drag-Drop

In my Application i have one NSOutlineView having list of somefiles and one WebView,
User allows to drag any item from the Outline view to WebView, and on that , i am suppose to handle the database transaction,
In the Outline view , i have implemented following methods,
- (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard{
[self log:#"write Items”];
// Some other code to prepare the Write Item,
}
- (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id < NSDraggingInfo >)info proposedItem:(id)item proposedChildIndex:(NSInteger)index{
}
- (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id < NSDraggingInfo >)info item:(id)item childIndex:(NSInteger)index{
}
On the WebView side, i have implemented following, methods,
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender {
NSPasteboard *pboard;
NSDragOperation sourceDragMask;
[self log:#"Inside draggingEntered”];
return NSDragOperationEvery;
}
- (BOOL)prepareForDragOperation:(id < NSDraggingInfo >)sender{
}
- (BOOL)performDragOperation:(id < NSDraggingInfo >)sender{
}
Now when i drag an element from outline view to WebView, i could see, writeItem of Outline view is getting called,
and in the Webview, DragEnter is getting called from where, i am not returning drag operation NONE, but returning NSDragoperationEvery,
The problem is that, i am not getting method, prepareForDragOperation and PerformDragOperation,
Can anyone help me please,
Kind Regards
Rohan
Hi guys
Thanks for Looking over it,
It got resolved by overriding
- (NSDragOperation)draggingUpdated:(id < NSDraggingInfo >)sender{
[self log:#"Inside Dragging updated"];
return NSDragOperationEvery;
}
Though in the document it was mentioned as an option and if its not implemented, it will take the return values from dragEnter Method,
perhaps for My own data type, it would have got NONE form WebView method and working once i overwrite it.
Kind Regard
Rohan

Help With Intra-Application Drag and Drop -- Cocoa

Okay, I've got a custom class called "Task", which represents a task to be done. I've got an NSMatrix which acts as a calendar. I want the user to be able to drag an icon from an nscollectionview (I've had no trouble setting up the nscollectionview) onto a cell in the nsmatrix, thereby assigning that task to that day. I just can't seem to get the nsmatrix to respond to the drag or the drop at all.
I've implemented the method:
- (BOOL)collectionView:(NSCollectionView *)cv writeItemsAtIndexes:(NSIndexSet *)indexes toPasteboard:(NSPasteboard *)pasteboard
{
[pasteboard declareTypes:[NSArray arrayWithObject:TASK_UTI] owner:self];
NSUInteger index=[indexes firstIndex];
Task* task=[[cv content] objectAtIndex:index];
NSData* taskData=[NSKeyedArchiver archivedDataWithRootObject:task];
[taskData retain];
BOOL success=[pasteboard setData:taskData forType:TASK_UTI];
return success;
}
in my nscollectionview delegate as shown above.
I've sent [self registerForDraggedTypes:[NSArray arrayWithObjects:TASK_UTI, nil]] in my NSMatrix subclass (called "Calendar").
I've implemented the methods:
- (NSDragOperation)draggingEntered:(id < NSDraggingInfo >)sender
- (BOOL)prepareForDragOperation:(id < NSDraggingInfo >)sender
- (BOOL)performDragOperation:(id < NSDraggingInfo >)sender
in my Calendar (NSMatrix subclass) class.
Some debugging shows that the NSMatrix/Calendar object is not even running the dragging methods above. What gives?
Did you define your Calendar class to implement dragging destination protocol?
First off, you should use your own domain name, not “com.yourcompany”, in the UTI.
Second, did you export the UTI in your Info.plist?