Search code examples
macosswiftcocoanstokenfield

Dropping a token in NSTokenField


I am implementing an app where rows from an NSTableView can be dragged and dropped into an NSTokenField, but I am struggling to implement the drop-side of the interaction. I have subclassed NSTokenField (as shown below in the debugging code below). But I am only seeing calls to draggingEntered: and updateDraggingItemsForDrag: method. Even though I return a valid NSDragOperation (Copy), none of the other methods in NSDraggingDestination are called. The cursor briefly flashes to the copy icon when moving over the token field, but then returns to the normal cursor.

I tried implementing all the methods associated with NSDraggingDestination for debugging purposes, shown in the code below. Is there another class or part of the NSTokenField that is handling the drop? Is it possible to override that?

I have confirmed that the pasteboard does have data with the valid pasteboard type.

let kPasteboardType = "SamplePasteboardType"

class MyTokenField : NSTokenField
{
    override func draggingEntered(sender: NSDraggingInfo) -> NSDragOperation {
        // entered
        NSLog("ENTERED")

        // must come from same window
        guard self.window == sender.draggingDestinationWindow() else {
            return super.draggingEntered(sender)
        }


        // has valid pasteboard data?
        let pb = sender.draggingPasteboard()
        if let _ = pb.dataForType(kPasteboardType) {
            NSLog("MATCHED")
            return NSDragOperation.Copy
        }

        return super.draggingEntered(sender)
    }

    override func draggingUpdated(sender: NSDraggingInfo) -> NSDragOperation {
        NSLog("UPDATED")

        // must come from same window
        guard self.window == sender.draggingDestinationWindow() else {
            return super.draggingUpdated(sender)
        }

        // has valid pasteboard data?
        let pb = sender.draggingPasteboard()
        if let _ = pb.dataForType(kPasteboardType) {
            return NSDragOperation.Copy
        }

        return super.draggingUpdated(sender)
    }

    override func draggingExited(sender: NSDraggingInfo?) {
        NSLog("EXITED")

        super.draggingExited(sender)
    }

    override func prepareForDragOperation(sender: NSDraggingInfo) -> Bool {
        NSLog("PREPARE")

        return super.prepareForDragOperation(sender)
    }

    override func performDragOperation(sender: NSDraggingInfo) -> Bool {
        NSLog("PERFORM")

        return super.performDragOperation(sender)
    }

    override func concludeDragOperation(sender: NSDraggingInfo?) {
        NSLog("CONCLUDE")

        super.concludeDragOperation(sender)
    }

    override func draggingEnded(sender: NSDraggingInfo?) {
        NSLog("ENDED")

        super.draggingEnded(sender)
    }

    override func updateDraggingItemsForDrag(sender: NSDraggingInfo?) {
        // super.updateDraggingItemsForDrag(sender)
        guard let drag = sender else {
            return
        }

        let classes: [AnyClass] = [NSPasteboardItem.self]
        let options: [String: AnyObject] = [NSPasteboardURLReadingContentsConformToTypesKey: [kPasteboardType]]

        drag.enumerateDraggingItemsWithOptions(NSDraggingItemEnumerationOptions.ClearNonenumeratedImages, forView: self, classes: classes, searchOptions: options) {
            (item, idx, stop) in
            NSLog("\(item)")
        }
    }
}

Solution

  • Thanks to the comment from @stevesliva, I was able to solve the problem. There are some key caveats that I discovered (they may be partly due to my ignorance of pasteboard and drag/drop interactions).

    1. Subclassing the NSTokenField class is not necessary.

    2. I had to implement the delegate function tokenField(tokenField: NSTokenField, readFromPasteboard pboard: NSPasteboard) -> [AnyObject]? for the token field.

    3. I had to change start of the drag to store a string value to the pasteboard. It seems like if the pasteboard does not have a string value, then the above delegate function is never called.