Search code examples
swiftcocoanstableview

Swift: NSTableView drag to reorder item how to display cell during dragging


I am implementing a drag and drop to reorder the cell, everything works but I would like that during dragging the cell view follows the mouse.

I'm using NSTableRowView. I need to make it work with NSTableRowView. If you have an idea how to do ..

The actually result:

enter image description here

My actually code:

    public func tableView(_ tableView: NSTableView, pasteboardWriterForRow row: Int) -> NSPasteboardWriting? {
        let account = viewModel.formattedServices[row]

        let pasteboardItem = NSPasteboardItem()
        pasteboardItem.setString(account.id, forType: NSPasteboard.PasteboardType(rawValue: "mymoney.account"))
        return pasteboardItem
    }

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

        if dropOperation == .above {
            return .move
        } else {
            return []
        }
    }

    public func tableView(_ tableView: NSTableView, acceptDrop info: NSDraggingInfo, row: Int, dropOperation: NSTableView.DropOperation) -> Bool {
        guard let item = info.draggingPasteboard.pasteboardItems?.first,
              let theString = item.string(forType: NSPasteboard.PasteboardType(rawValue: "mymoney.account")),
              let account = viewModel.formattedServices.first(where: { $0.id == theString }),
              let originalRow = viewModel.formattedServices.firstIndex(where: { $0.id == account.id })
                else { return false }

        var newRow = row
        // When you drag an item downwards, the "new row" index is actually --1. Remember dragging operation is `.above`.
        if originalRow < newRow {
            newRow = row - 1
        }

        // Animate the rows
        tableView.beginUpdates()
        tableView.moveRow(at: originalRow, to: newRow)
        tableView.endUpdates()

        return true
    }

I can't find a method to return the cell view during dragging.

For return cell view I'm using func tableView(_ tableView: NSTableView, rowViewForRow row: Int) -> NSTableRowView?

And my NSTableRowView:

class ServicesTableViewCell: NSTableRowView, CustomCelleable {

    @IBOutlet weak var titleLbl: NSTextField!
    @IBOutlet weak var notificationCount: NSButton!
    @IBOutlet weak var iconImageView: NSImageView!


    override func draw(_ dirtyRect: NSRect) {
        super.draw(dirtyRect)
        isEmphasized = false
    }

    func setup(with item: Celleable) { }

Solution

  • Step 1 NSTableRowView can't replace NSTableCellView, use NSTableCellView for the table cells.

    Starting with the test app. In IB, select the column in the table view and add a Text Table Cell View from the library.

    enter image description here

    In ViewController.swift replace tableView(_:rowViewForRow:) by tableView(_:viewFor:row:).

    func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
        guard let cell = tableView.makeView(withIdentifier: tableColumn!.identifier, owner: self) as? NSTableCellView else {
            fatalError()
        }
    
        cell.textField?.stringValue = array[row].name
    
        return cell
    }
    

    In setTableView() remove registering the nib.

    func setTableView() {
        tableView.delegate = self
        tableView.dataSource = self
        tableView.registerForDraggedTypes([NSPasteboard.PasteboardType(rawValue: "mymoney.account")])
    }
    

    ServicesTableViewCell isn’t used anymore.

    Step 2 Use a subclass of NSTableRowView to disable isEmphasized:

    Add a subclass of NSTableRowView, let’s name it ServicesTableRowView.

    Override isEmphasized to make it always false.

    class ServicesTableRowView: NSTableRowView {
    
        override var isEmphasized: Bool {
            get {
                return false
            }
            set {
                // do nothing
            }
        }
    
    }
    

    In IB select the table view and add a Custom View from the library. Set the class of the view to ServicesTableRowView and the identifier to NSTableViewRowViewKey.

    Xcode IB

    The modified test project: we.tl/t-yjpZrz6q7g