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
Related
My NSCollectionView is drawing my NSCollection items over each other.
UPDATE: I have added a sample project
GitHub Sample Project
UPDATE: This has changed somewhat
When the app first launches it looks like this
UPDATE
My current example has two views which are currently in there own nib files, with dedicated NScollectionViewItem objects they are currently the same for testing. I basically have a NSCollectionViewItem which has as it's child a view with the NSTextField in it. With all of the constraints.
For the Collection View it is setup as a Grid Controller, and ideally, I would like to have 1 column.
In order to load it with Data I made my ViewController the NSCollectionViewDataSource, and implemented the - (NSInteger)collectionView:(NSCollectionView *)collectionView numberOfItemsInSection:(NSInteger)section and - (NSCollectionViewItem *)collectionView:(NSCollectionView *)collectionView
itemForRepresentedObjectAtIndexPath:(NSIndexPath *)indexPath
UPDATED CODE
Full Code included:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
[collectionView registerClass:ItemOne.class forItemWithIdentifier:#"Item1"];
[collectionView registerClass:ItemTwo.class forItemWithIdentifier:#"Item2"];
cellArray = [#[#"Item1", #"Item2", #"Item1", #"Item2", #"Item1"] mutableCopy];
}
- (void)setRepresentedObject:(id)representedObject {
[super setRepresentedObject:representedObject];
// Update the view, if already loaded.
}
#pragma mark - NSCollectionViewDatasource -
- (NSInteger)collectionView:(NSCollectionView *)collectionView
numberOfItemsInSection:(NSInteger)section {
// We are going to fake it a little. Since there is only one section
NSLog(#"Section: %ld, count: %ld", (long)section, [cellArray count]);
return [cellArray count];
}
- (NSCollectionViewItem *)collectionView:(NSCollectionView *)collectionView
itemForRepresentedObjectAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"IndexPath: %#, Requested one: %ld", indexPath, [indexPath item]);
NSLog(#"Identifier: %#", [cellArray objectAtIndex:[indexPath item]]);
NSCollectionViewItem *theItem = [collectionView makeItemWithIdentifier:[cellArray objectAtIndex:[indexPath item]] forIndexPath:indexPath];
return theItem;
}
UPDATE
The ItemOne and ItemTwo classes are both empty classes, the nib for each has a NSCollectionViewItem which in turn has a view, with label. The View is connected to the NSCollectionViewItem by the view property in NSCollectionViewItem. There are currently no constraints except for the default ones
The NSCollectionView grid is set up as follows:
Layout: Grid Dimensions: Max Rows: 0 Max Columns: 1 Min Item Size:
Width: 250 Height: 150 Max Item Size: Width: 250 Height: 150
This is the code for setting up the whole thing, at this point not tying it to a data source.
It seems that no matter what I change the settings or even changing the CollectionView type to Flow doesn't change anything, it looks the same.
I have been approaching this as an AutoLayout issue because originally there were some auto layout issues, but those have all been resolved.
Any help would be greatly appreciated.
The data array should hold data instead of NSCollectionViewItems. In collectionView:itemForRepresentedObjectAtIndexPath: you call makeItemWithIdentifier:forIndexPath:. Call registerClass:forItemWithIdentifier: or registerNib:forItemWithIdentifier: to register your class or nib.
More info in the documentation of NSCollectionView, collectionView:itemForRepresentedObjectAtIndexPath: and makeItemWithIdentifier:forIndexPath:.
EDIT:
There are two ways to provide a NSCollectionViewItem.
registerClass:forItemWithIdentifier:. When the collection view needs a new item, it instatiates this class. NSCollectionViewItem is a subclass of NSViewController and NSViewController looks for a nib with the same name as the class. The NSCollectionViewItem is the owner of the nib.
registerNib:forItemWithIdentifier:. When the collection view needs a new item, it loads this nib. The NSCollectionViewItem is a top level object in the nib.
You mixed registerClass:forItemWithIdentifier: with a xib for use with registerNib:forItemWithIdentifier:. Use registerNib:forItemWithIdentifier: or fix the xib.
I have it figured out.
And have made a github repo with a working version Working Version of Collection View Sample Application
First thing. Thanks to Willeke's catch of the way the original xib was setup I was able to get the Grid type working. But in the end the Grow view is a better type of view if you can make it do what you want, because it support sections, and distances between views etc. So eventhough I started out wanting to use the Grid type I am going to implement the Grow type in my app.
So I accomplished a single column view using the Grow type.
My Criteria for success are:
That it can support non-uniform view heights (Each custom view can have it's own height)
That there is a single column, and each custom view expands if the view size expands.
Onto the source code:
#interface ViewController ()
#property NSMutableArray *cellArray;
#property (weak) IBOutlet NSCollectionView *collectionView;
#end
#implementation ViewController
#synthesize cellArray;
#synthesize collectionView;
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
[collectionView registerClass:ItemOne.class forItemWithIdentifier:#"Item1"];
[collectionView registerClass:ItemTwo.class forItemWithIdentifier:#"Item2"];
cellArray = [#[#"Item1", #"Item2", #"Item1", #"Item2", #"Item1"] mutableCopy];
}
- (void)setRepresentedObject:(id)representedObject {
[super setRepresentedObject:representedObject];
// Update the view, if already loaded.
}
#pragma mark - NSCollectionViewDatasource -
- (NSInteger)numberOfSectionsInCollectionView:(NSCollectionView *)collectionView {
return 1;
}
- (NSInteger)collectionView:(NSCollectionView *)collectionView
numberOfItemsInSection:(NSInteger)section {
// We are going to fake it a little. Since there is only one section
NSLog(#"Section: %ld, count: %ld", (long)section, [cellArray count]);
return [cellArray count];
}
- (NSCollectionViewItem *)collectionView:(NSCollectionView *)collectionView
itemForRepresentedObjectAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"IndexPath: %#, Requested one: %ld", indexPath, [indexPath item]);
NSLog(#"Identifier: %#", [cellArray objectAtIndex:[indexPath item]]);
NSCollectionViewItem *theItem = [collectionView makeItemWithIdentifier:[cellArray objectAtIndex:[indexPath item]] forIndexPath:indexPath];
theItem.representedObject = [cellArray objectAtIndex:[indexPath item]];
return theItem;
}
#pragma mark - NSCollectionViewDelegate -
- (NSSize)collectionView:(NSCollectionView *)collectionView
layout:(NSCollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"%#", indexPath);
NSSize size = NSMakeSize(438, 150);
NSInteger width = 0;
NSInteger height = 0;
NSString *label = [cellArray objectAtIndex:[indexPath item]];
NSRect collectionFrame = [collectionView frame];
width = collectionFrame.size.width;
// TODO: This needs to be based on the actual value of the view instead of hardcoding a number in.
if ([label isEqualToString:#"Item1"]) {
height = 114;
} else if ([label isEqualToString:#"Item2"]) {
height = 84;
}
size = NSMakeSize(width, height);
return size;
}
#end
And there you have it. The implementation wasn't too bad. Each of the Custom views that show up in the NSCollectionView are defined in there own NSCollectionViewItem and .xib file, so they are easily modifiable.
The only part that is brittle is where I am calculating the height of each view, and it is only brittle because I am being lazy in my implementation in the sample application. In the actual implementation I will dynamically grab them from the actual views, so that they aren't tied to a static number.
i have created the NSViewController for managing the PXListView....delegate is connect with File owner...
here everything is working as per the expectation..except drag and drop...
while dragging it is not entering into the dragging delegate function...and it is not showing the + symbol while dragging(i.e dragging symbol)... and dragging cells relocates to its original position
#import "PXListView.h"
#interface ListViewController : NSViewController <PXListViewDelegate>
{
IBOutlet PXListView *listView;
NSMutableArray *_listItems;
int count;
}
- (void)addCell;
#end
- (BOOL)listView:(PXListView*)aListView writeCellsWithIndexes:(NSIndexSet*)rowIndexes toPasteboard:(NSPasteboard*)dragPasteboard
{
// +++ Actually drag the items, not just dummy data.
[dragPasteboard declareTypes: [NSArray arrayWithObjects: NSStringPboardType, nil] owner: self];
[dragPasteboard setString: #"Just Testing" forType: NSStringPboardType];
return YES;
}
- (NSDragOperation)listView:(PXListView*)aListView validateDrop:(id <NSDraggingInfo>)info proposedCell:(NSUInteger)row
proposedDropHighlight:(PXListViewDropHighlight)dropHighlight;
{
return NSDragOperationCopy;
}
- (BOOL)listView:(PXListView*)aListView acceptDrop:(id <NSDraggingInfo>)info row: (NSUInteger)row dropHighlight:(PXListViewDropHighlight)dropHighlight
{
NSLog(#"Accept Drop");
ListViewThumbnailObject *temp = [_listItems objectAtIndex: [listView selectedCell]];
[_listItems removeObjectAtIndex: [listView selectedCell]];
[_listItems insertObject: temp atIndex: row];
[listView reloadData];
return YES;
}
it is not entering into the drag related delegate methods..
in the "nib" file delegate is wired with file owner...
can anyone please suggest me how to solve this problem?
Thanks,
Muthu
To be able to accept drops, you will likely need to assure you register the list view for all dragged types you plan on accepting. Try adding this in the awakeFromNib method in your list view controller class:
- (void)awakeFromNib {
[listView registerForDraggedTypes:[NSArray
arrayWithObjects:NSStringPboardType, nil]];
}
See Drag and Drop Programming Topics: Receiving Drag Operations for more info.
Note that I used NSStringPboardType in the array here because that's the type you declared for writing in the listView:writeCellsWithIndexes:toPasteboard: method. The exact array of types you use will depend on the types you want to support, and the type of drag operations you want to support (for example, do you just want to implement drag and drop to allow reordering of items in a single list view, or do you want to allow dragging items from the Finder, or to the Finder, or between different views in your own application). If you describe the type of drag operations you hope to support I can provide more details on how you might want to implement them.
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.
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.
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?