0

To keep it simple, I used this URL to create a macOS table in Swift (4.1): https://medium.com/@kicsipixel/very-simple-view-based-nstableview-in-swift-ab6d7bb30fbb

I then used this URL to be able to drag and drop rows in the table: http://bit.boutique/blog/2015/6/8/drag-sorting-nstableview-rows-in-swift/

I able to determine how to double click to edit a cell by using this URL: Double click an NSTableView row in Cocoa?

If the table has multiple columns, to determine what column was being edited, I had to open a problem request with Apple. They provided me with this undocumented piece of code (and suggested I open a documentation bug report, which I did).

func control(_ control: NSControl, textShouldEndEditing fieldEditor: NSText) -> Bool {
    print("textShouldEndEditing text is " + (fieldEditor.string) + " [" +  String(describing:  tableView.row(for: control)) + "][" + String(describing: tableView.column(for: control) ) + "]" )
    return true
} // textShouldEndEditing

I pieced all of that together and I thought I was done. I then attempted to add another column to my table, an NSPopUpButton. When I did that the table displays fine but I can no longer drag and drop the rows.

The functions viewFor, writeRowsWith, and DraggingSession endedAt is called but validateDrop and acceptDrop are not.

// MARK: tableView
func numberOfRows(in tableView: NSTableView) -> Int {

    tableView.doubleAction = #selector(doubleClickOnResultRow)

    initPrefs()

    tableView.tableColumns[0].title = localizedString(forKey: "CityNames_") + ":"
    tableView.tableColumns[1].title = localizedString(forKey: "CityDisplayNames_") + ":"
    tableView.tableColumns[2].title = localizedString(forKey: "weatherSource_") + ":"
    tableView.tableColumns[3].title = localizedString(forKey: "API Key 1:_")
    tableView.tableColumns[4].title = localizedString(forKey: "API Key 2:_")

    return locationInformationArray.count
} // numberOfRows

// Populate table
func tableView(_ tableView: NSTableView,
               viewFor tableColumn: NSTableColumn?,
               row: Int) -> NSView? {
    var cell: NSTableCellView

    //print("viewFor: row=" + String(describing:  row), column=" + String(describing:  column) )

    var column = -1
    if tableColumn == tableView.tableColumns[0] {
        column = 0
    } else  if tableColumn == tableView.tableColumns[1] {
        column = 1
    } else  if tableColumn == tableView.tableColumns[2] {
        column = 2
    } else  if tableColumn == tableView.tableColumns[3] {
        column = 3
    } else {
        column = 4
    }

    if (column != 2) {
        cell = (tableView.makeView(withIdentifier: tableColumn!.identifier, owner: nil) as? NSTableCellView)!
        cell.textField?.stringValue = locationInformationArray[row][column]
    } else { // Column 2/Weather Source
        let result = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "weatherSource"), owner: nil) as! NSPopUpButton
        InitWeatherSourceButton(weatherSourceButton: result)
        result.selectItem(at: Int(locationInformationArray[row][column])!)
        return result
    }

    return cell
} // viewFor (Populate)

// Drag "from"
func tableView(_ tableView: NSTableView,
               writeRowsWith writeRowsWithIndexes: IndexSet,
               to toPasteboard: NSPasteboard) -> Bool {
    print("in writeRowsWith")
    let data = NSKeyedArchiver.archivedData(withRootObject: [writeRowsWithIndexes])
    toPasteboard.declareTypes([NSPasteboard.PasteboardType(rawValue: MyRowType)], owner:self)
    toPasteboard.setData(data, forType:NSPasteboard.PasteboardType(rawValue: MyRowType))

    return true
} // writeRowsWith (From Row)

// Drag "to"
func tableView(_ tableView: NSTableView,
               validateDrop info: NSDraggingInfo,
               proposedRow row: Int,
               proposedDropOperation dropOperation: NSTableView.DropOperation) -> NSDragOperation {

    print("in validateDrop, proposedRow=" + String(describing: row))
    tableView.setDropRow(row, dropOperation: NSTableView.DropOperation.above)
    return NSDragOperation.move
} // validateDrop

// Drag "to"
func tableView(_ tableView: NSTableView,
               acceptDrop info: NSDraggingInfo,
               row: Int,
               dropOperation: NSTableView.DropOperation) -> Bool {
    print("in acceptDrop, row=" + String(describing: row))
    let pasteboard = info.draggingPasteboard()
    let rowData = pasteboard.data(forType: NSPasteboard.PasteboardType(rawValue: MyRowType))

    if(rowData != nil) {
        var dataArray = NSKeyedUnarchiver.unarchiveObject(with: rowData!) as! Array<IndexSet>,
        indexSet = dataArray[0]

        let movingFromIndex = indexSet.first

        //tableView.moveRow(at: movingFromIndex!, to: row) // Can only be done if the Array doesn't need to get re-populated
        _moveItem(from: movingFromIndex!, to: row, array: &locationInformationArray)

        tableView.reloadData()
        return true
    }
    else {
        return false
    }
} // acceptDrop

func tableView(_ tableView: NSTableView,
               draggingSession session: NSDraggingSession,
               endedAt screenPoint: NSPoint,
               operation: NSDragOperation) {
        return
}


// Move row in table array
func _moveItem(from: Int,
               to: Int,
               array: inout [[String]]) {
    //print("in _moveItem")
    let item = array[from]
    array.remove(at: from)

    if(to > array.endIndex) {
        array.append(item)
    }
    else {
        array.insert(item, at: to)
    }
} // _moveItem

And finally, I can't determine which row (and column) is being access by the NSPopUpButton:

@IBAction func popUpSelectionDidChange(_ sender: NSPopUpButton) {
    print("Selected item=" + String(describing: sender.indexOfSelectedItem) + " [" +  String(describing:  tableView.row(for: tableView)) + "][" + String(describing: tableView.column(for: tableView)) + "]" )
}

Both the row and column are always -1.

Any suggestions on how to solve both of my remaining issues (in Swift), drag and drop and which Pop Up button was selected?

Thanks.

Ed Danley
  • 63
  • 9
  • Why do you need code to enable double click to start edit? From where do you want to know which column is being edited? To debug drag and drop we need all the relevant code. – Willeke Apr 03 '18 at 07:54
  • `NSTextField` and `NSPopUpButton` are subclasses of `NSControl`. `tableView.row(for: control)` and `tableView.column(for: control)` will work for both. – Willeke Apr 03 '18 at 07:56
  • Wileke - thank you for the (for: control) note, this now works: @IBAction func popUpSelectionDidChange(_ sender: NSPopUpButton) { print("Selected item=" + String(describing: sender.indexOfSelectedItem) + " [" + String(describing: tableView.row(for: sender)) + "][" + String(describing: tableView.column(for: sender)) + "]" ) } As for the drag and drop, this is my complete set of code, I've edited my original post. – Ed Danley Apr 05 '18 at 03:00
  • Wileke - If you wanted to post your second comment as an answer (subclass of NSControl), I'll accept it as that solved the click location. The answer provided by Rob Mayoff solved the second part (drag and drop). – Ed Danley Apr 05 '18 at 21:33

1 Answers1

0

You'll save some code if you change the type of MyRowType to NSPasteboard.PasteboardType, e.g.

fileprivate let MyRowType = NSPasteboard.PasteboardType("com.ed-danley.MyRowType")

The tableView:writeRowsWithIndexes:toPasteboard: and tableView:draggingSession:endedAtPoint: messages are for dragging source support (check NSTableView.h to verify).

The tableView:validateDrop:proposedRow:proposedDropOperation: and tableView:acceptDrop:row:dropOperation: messages are for dragging destination support. My guess is you have not configured the table as a drag destination.

To configure the table view as a drag destination, you must call tableView.registerForDraggedTypes([MyRowType]). This is actually a method on NSView and it's what tells AppKit that you want this view to be a drag destination. You only need to call this once on the table view, so depending on what kind of controller you're using, viewDidLoad or windowDidLoad may be the appropriate place.

rob mayoff
  • 375,296
  • 67
  • 796
  • 848
  • Thanks Rob. However for "you have not configured the table as a drag destination", the drag and drop worked until I added the Pop Up and it works again if I remove it. Is there a different way to configure it with the Pop Up? – Ed Danley Apr 05 '18 at 11:53
  • Ron - The fileprivate and registerForDraggedTypes did the trick. Thank you. I upvoted the answer but I don't have enough points to be accepted. – Ed Danley Apr 05 '18 at 21:34