How to drag items from NSCollectionView

879 Views Asked by At

I'm trying to implement dragging items from an NSCollectionView (not just dropping things on it).

In my example code, I'm registering the CollectionView from dragging:

collectionView.registerForDraggedTypes([.URL])
collectionView.setDraggingSourceOperationMask(.every, forLocal: false)
collectionView.setDraggingSourceOperationMask(.every, forLocal: true)

Then, I've implemented these methods from the NSCollectionViewDelegate protocol:

func collectionView(_ collectionView: NSCollectionView, canDragItemsAt indexPaths: Set<IndexPath>, with event: NSEvent) -> Bool {
    return true
}

func collectionView(_ collectionView: NSCollectionView, pasteboardWriterForItemAt indexPath: IndexPath) -> NSPasteboardWriting? {    
    return URL(fileURLWithPath: #file) as NSPasteboardWriting
}

func collectionView(_ collectionView: NSCollectionView, draggingSession session: NSDraggingSession, willBeginAt screenPoint: NSPoint, forItemsAt indexPaths: Set<IndexPath>) { }

func collectionView(_ collectionView: NSCollectionView, draggingSession session: NSDraggingSession, endedAt screenPoint: NSPoint, dragOperation operation: NSDragOperation) { }

But neither of them is ever even called! Why not?

If I add these two methods:

func collectionView(_ collectionView: NSCollectionView, validateDrop draggingInfo: NSDraggingInfo, proposedIndexPath proposedDropIndexPath: AutoreleasingUnsafeMutablePointer<NSIndexPath>, dropOperation proposedDropOperation: UnsafeMutablePointer<NSCollectionView.DropOperation>) -> NSDragOperation {
    return .move
}

func collectionView(_ collectionView: NSCollectionView, acceptDrop draggingInfo: NSDraggingInfo, indexPath: IndexPath, dropOperation: NSCollectionView.DropOperation) -> Bool {
    return true
}

Then I can successfully drop files from the Desktop into the collection view, but still not the other way around.

What's going on?

Best regards, V.

1

There are 1 best solutions below

0
backslash-f On

Exactly the same here.

It looks like a bug (Xcode 9.4.1, Swit 4.1.2).

Just like you mentioned, validateDrop and acceptDrop get called in my app.
But (for example), endedAt does not:

func collectionView(_ collectionView: NSCollectionView,
                    draggingSession session: NSDraggingSession,
                    endedAt screenPoint: NSPoint,
                    dragOperation operation: NSDragOperation) { }

The only workaround I could find (for the above endedAt delegate) was to subclass NSCollectionView (which I'm calling ImageCollectionView in the example below) and implement my own delegate/protocol:

import Cocoa

protocol ImageCollectionViewDelegate {
    func didExitDragging()
}

class ImageCollectionView: NSCollectionView {
    var imageCollectionViewDelegate: ImageCollectionViewDelegate?
}

extension ImageCollectionView {
    override func draggingExited(_ sender: NSDraggingInfo?) {
        super.draggingExited(sender)
        imageCollectionViewDelegate?.didExitDragging()
    }
}

(I had to call it imageCollectionViewDelegate to not conflict with the already existing delegate property.)

Then, in my controller (which I'm calling ImageCollectionViewController):

@IBOutlet internal weak var imageCollectionView: ImageCollectionView! {
    didSet {
        imageCollectionView.imageCollectionViewDelegate = self
    }
}

extension ImageCollectionViewController: ImageCollectionViewDelegate {
    func didExitDragging() {
        highlightDestination(false)
    }
}

That allows me to do stuff when dragging outside the collection.
In this very simple use case, just turning the destination view's highlight on/off:

highlighting

Ideally all this extra code wouldn't be necessary.

I'm also looking for the proper way of handling this.