0
votes

I have an NSTableView where I can drag and drop table rows to reorder them. This works by setting a drag type in my view controller:

@IBOutlet weak var tableView: NSTableView!
let dragType = NSPasteboard.PasteboardType(rawValue: "myapp.task")

override func viewDidLoad() {
  super.viewDidLoad()
  tableView.registerForDraggedTypes([dragType])
}

...and then implementing the reordering with these table delegate methods:

//Start drag
func tableView(_ tableView: NSTableView, pasteboardWriterForRow row: Int) -> NSPasteboardWriting? {
  let item = NSPasteboardItem()
  item.setString(String(row), forType: dragType)
  return item
}

//Verify proposed drop
func tableView(_ tableView: NSTableView, validateDrop info: NSDraggingInfo, proposedRow row: Int, proposedDropOperation dropOperation: NSTableView.DropOperation) -> NSDragOperation {
  if dropOperation == .above {
    return .move
  }else{
    return []
  }
}

//Accept drop of one or multiple rows
func tableView(_ tableView: NSTableView, acceptDrop info: NSDraggingInfo, row: Int, dropOperation: NSTableView.DropOperation) -> Bool {
  var oldIndexes = [Int]()
  info.enumerateDraggingItems(options: [], for: tableView, classes: [NSPasteboardItem.self], searchOptions: [:]) { dragItem, _, _ in
    if let str = (dragItem.item as! NSPasteboardItem).string(forType: self.dragType), let index = Int(str) {
      oldIndexes.append(index)
    }
  }

  //Do a bunch of logic to reorder the table rows...
}

Now, in addition to reordering my table rows, I want to be able to drag a row and drop it somewhere else in my app--sort of like moving the row to a different place.

I have a custom NSView set up as the drag destination for this, and I can drag a table row and the custom view reacts appropriately with a table row dragged over it:

class MyCustomView: NSView{

  required init?(coder: NSCoder) {
    super.init(coder: coder)
    let taskDragType = NSPasteboard.PasteboardType(rawValue: "myapp.task")
    registerForDraggedTypes([taskDragType])
  }

  override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation {
    //...
  }
  override func draggingExited(_ sender: NSDraggingInfo?) {
    //...
  }
}

But the part I'm unclear on is how to get the table row, and its associated object set as a property on the NSTableCellView, when the drop occurs:

//This is another method in MyCustomView

override func performDragOperation(_ draggingInfo: NSDraggingInfo) -> Bool {

  guard let items = draggingInfo.draggingPasteboard.pasteboardItems else{ return false }

  for item in items{
    print("---")
    print(item) //<-- NSPasteboardItem
    let index = item.propertyList(forType: NSPasteboard.PasteboardType(rawValue: "myapp.task"))
    print(index) //<-- Index of the table row
    //How can I also get the task object associated with the row?
  }
}

I can get the index of the row, but what I need is the entire object from the row's data source so I can take action on the object it represents. My suspicion is that I need to change how I'm using pasteboardWriterForRow to put my object on the pasteboard, but I'm unsure how to do that.

How can I pass both the row index and the object to the pasteboard?

1

1 Answers

0
votes

Soon after posting this, I decided to try something crazy, and it turns out I found a way to make this a lot simpler. It seems that NSPasteboard is really only necessary if you need to get stuff into and out of your app. Since I am just moving something from one part of my app to another, I can use the drag and drop delegate methods as events and handle the data myself.

First, I set up a global array for adding dragged task objects:

var draggedTasks = [Task]()

Whenever a task is dragged from my NSTableView, I add them to the array in the aforementioned delegate method where dragging starts:

//Start drag
func tableView(_ tableView: NSTableView, pasteboardWriterForRow row: Int) -> NSPasteboardWriting? {
  //Queue tasks for moving to phases or projects
  draggedTasks.append(tasks[row])

  //Queue row for reordering
  let item = NSPasteboardItem()
  item.setString(String(row), forType: dragType)
  return item
}

Then where I accept the drop in MyCustomView, I take action on the draggedTasks array:

//Save dropped tasks
override func performDragOperation(_ draggingInfo: NSDraggingInfo) -> Bool {
  //Do stuff to draggedTasks based on the context of where they are dropped
  return true
}

This is much simpler than going down the NSPasteboard route. 🙂