Question

I have an NSCollectionView bound to an NSArrayController. I want to get drag and drop working, so I create a delegate and implement the methods

-(BOOL)collectionView:(NSCollectionView *)collectionView canDragItemsAtIndexes:(NSIndexSet *)indexes withEvent:(NSEvent*)event
-(BOOL)collectionView:(NSCollectionView *)collectionView acceptDrop:(id < NSDraggingInfo >)draggingInfo index:(NSInteger)index dropOperation:(NSCollectionViewDropOperation)dropOperation
-(NSDragOperation)collectionView:(NSCollectionView *)collectionView validateDrop:(id < NSDraggingInfo >)draggingInfo proposedIndex:(NSInteger *)proposedDropIndex dropOperation:(NSCollectionViewDropOperation *)proposedDropOperation
-(NSArray *)collectionView:(NSCollectionView *)collectionView namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropURL forDraggedItemsAtIndexes:(NSIndexSet *)indexes

I'm returning YES for the two BOOL methods, NSDragOperationMove for the validateDrop: method, and an empty array for the namesOfPromisedFilesDroppedAtDestination: method. I also have an NSLog statement as the first line in each method so I can see when they get called.

Right now, the only method that gets called is canDragItemsAtIndexes: (where I return YES). I see that it gets called, but any further dragging just modifies the selection. The rest never get called.

If I make the NSCollectionView not support selections, then not even that method gets called.

I'm sure I'm missing something super obvious, but I can't figure out what it is. Has anyone gotten drag and drop working with NSCollectionViews and can shed some light?

Was it helpful?

Solution

I think you miss the part where you write the drag content to the pasteboard.
To support drag and drop you have to perform the following steps:

  1. Determine if you can drag in your drag source
  2. If YES, write the content to the Pasteboard
  3. Validate & accept the items in your drop target

Writing to the Pasteboard should be implemented in
- collectionView:writeItemsAtIndexes:toPasteboard:

You also have to register your dragged types with - registerForDraggedTypes:

Some sample code: http://developer.apple.com/library/mac/#samplecode/IconCollection/Introduction/Intro.html

OTHER TIPS

This code has everything I needed to drag an image from one NSCollectionView to another. Figuring this out was NOT super obvious. Selectable is checked for the source collection view and wired up for the dataSource and delegate outlets, but I did not need registerForDraggedTypes.

class Window:NSWindow, NSComboBoxDelegate, NSTextFieldDelegate, NSDatePickerCellDelegate, NSTableViewDataSource, NSTableViewDelegate, MKMapViewDelegate, NSCollectionViewDataSource, NSCollectionViewDelegate, NSCollectionViewDelegateFlowLayout, NSTabViewDelegate, NSMenuDelegate, NSDraggingDestination { }

    func collectionView(collectionView: NSCollectionView, writeItemsAtIndexPaths indexPaths: Set<NSIndexPath>, toPasteboard pasteboard: NSPasteboard) -> Bool {
    let index = indexPaths.first!.item
    let url = webImageURLs[index]   // array of string URLs that parallels the collection view.
    NSPasteboard.generalPasteboard().clearContents()
    NSPasteboard.generalPasteboard().declareTypes([kUTTypeText as String, kUTTypeData as String], owner: nil)
    NSPasteboard.generalPasteboard().setString(url, forType: (kUTTypeText as String))
    NSPasteboard.generalPasteboard().setData(webImageData[index], forType: (kUTTypeData as String))
    return true
}

// Provide small version of image being dragged to accompany mouse cursor.
func collectionView(collectionView: NSCollectionView, draggingImageForItemsAtIndexPaths indexPaths: Set<NSIndexPath>, withEvent event: NSEvent, offset dragImageOffset: NSPointPointer) -> NSImage {
    let item = collectionView.itemAtIndex(indexPaths.first!.item)
    return (item?.imageView?.image)!.resizeImage(20, height: 20)
}

// Image is dropped on destination NSCollectionView.
func collectionView(collectionView: NSCollectionView, draggingSession session: NSDraggingSession, endedAtPoint screenPoint: NSPoint, dragOperation operation: NSDragOperation) {
    let pasteboardItem = NSPasteboard.generalPasteboard().pasteboardItems![0]
    let urlString = pasteboardItem.stringForType((kUTTypeText as String))
    let imageData = pasteboardItem.dataForType((kUTTypeData as String))

    // destinationImages is the data source for the destination collectionView. destinationImageURLs is used to keep track of the text urls.
    if urlString != nil {
        destinationImageURLs.insert(urlString!, atIndex: 0)
        destinationImages.insert(NSImage(data: imageData!)!, atIndex: 0)
        destinationCollectionView.reloadData()
        let selectionRect = self.favoritesCollectionView.frameForItemAtIndex(0)
        destinationCollectionView.scrollRectToVisible(selectionRect)
    }
}

extension NSImage {
    func resizeImage(width: CGFloat, height: CGFloat) -> NSImage {
        let img = NSImage(size: CGSizeMake(width, height))
        img.lockFocus()
        let ctx = NSGraphicsContext.currentContext()
        ctx?.imageInterpolation = .High
        drawInRect(NSRect(x: 0, y: 0, width: width, height: height), fromRect: NSRect(x: 0, y: 0, width: size.width, height: size.height), operation: .CompositeCopy, fraction: 1)
        img.unlockFocus()

        return img
    }
} 
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top