macos - reviews - imdd



Trascina e rilascia riordina righe su NSTableView (4)

Mi stavo chiedendo se esistesse un modo semplice per impostare un NSTableView per consentirgli di riordinare le sue righe senza scrivere alcun codice pasteboard. Ho solo bisogno che sia in grado di farlo internamente, all'interno di un tavolo. Non ho alcun problema a scrivere il codice pboard, tranne che sono abbastanza sicuro di aver visto Interface Builder avere un interruttore per questo da qualche parte / visto funzionare di default. Sembra certamente un compito abbastanza comune.

Grazie

https://src-bin.com


Answer #1

Sfortunatamente devi scrivere il codice della scheda Paste. L'API Drag and Drop è abbastanza generica, il che lo rende molto flessibile. Tuttavia, se hai solo bisogno di riordinare è un IMHO un po 'esagerato. In ogni caso, ho creato un piccolo progetto di esempio con un NSOutlineView cui è possibile aggiungere e rimuovere elementi e riordinarli.

Questo non è un NSTableView ma l'implementazione del protocollo Drag & Drop è sostanzialmente identica.

Ho implementato il drag and drop in una volta sola, quindi è meglio guardare questo commit .


Answer #2

Questa risposta copre Swift 3 , NSTableView s basato su View e riordino di drag & drop su righe singole / multiple.

Ci sono 2 passaggi principali che devono essere eseguiti al fine di ottenere questo:

  • Registra la vista tabella in un tipo di oggetto specificamente consentito che può essere trascinato.

    tableView.register(forDraggedTypes: ["SomeType"])

  • Implementa 3 metodi NSTableViewDataSource : writeRowsWith , validateDrop e acceptDrop .

Prima che l'operazione di trascinamento sia iniziata, memorizza IndexSet con gli indici delle righe che verranno trascinate, nel IndexSet di IndexSet .

func tableView(_ tableView: NSTableView, writeRowsWith rowIndexes: IndexSet, to pboard: NSPasteboard) -> Bool {

        let data = NSKeyedArchiver.archivedData(withRootObject: rowIndexes)
        pboard.declareTypes(["SomeType"], owner: self)
        pboard.setData(data, forType: "SomeType")

        return true
    }

Convalida il rilascio solo se l'operazione di trascinamento è al di sopra della riga specificata. Ciò garantisce che quando si esegue il trascinamento altre righe non vengano evidenziate quando la riga trascinata galleggerà sopra di esse. Inoltre, questo risolve un problema di AutoLayout .

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

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

Quando si accetta drop, basta recuperare IndexSet che in precedenza era stato salvato nel IndexSet , scorrere iterate e spostare le righe utilizzando gli indici calcolati. Nota: parte con iterazione e spostamento di riga che ho copiato dalla risposta di @ Ethan.

    func tableView(_ tableView: NSTableView, acceptDrop info: NSDraggingInfo, row: Int, dropOperation: NSTableViewDropOperation) -> Bool {
        let pasteboard = info.draggingPasteboard()
        let pasteboardData = pasteboard.data(forType: "SomeType")

        if let pasteboardData = pasteboardData {

            if let rowIndexes = NSKeyedUnarchiver.unarchiveObject(with: pasteboardData) as? IndexSet {
                var oldIndexOffset = 0
                var newIndexOffset = 0

                for oldIndex in rowIndexes {

                    if oldIndex < row {
                        // Dont' forget to update model

                        tableView.moveRow(at: oldIndex + oldIndexOffset, to: row - 1)
                        oldIndexOffset -= 1
                    } else {
                        // Dont' forget to update model

                        tableView.moveRow(at: oldIndex, to: row + newIndexOffset)
                        newIndexOffset += 1
                    }
                }
            }
        }

        return true
    }

Gli aggiornamenti di NSTableView basati su moveRow vengono richiamati quando viene chiamato moveRow , non è necessario utilizzare il beginUpdates() e endUpdates() .


Answer #3

Imposta l'origine dati della vista tabella come classe conforme a NSTableViewDataSource .

Metti questo in un posto appropriato ( -awakeFromNib , -viewDidLoad , -viewDidLoad o qualcosa di simile):

tableView.registerForDraggedTypes(["public.data"])

Quindi implementare questi tre metodi NSTableViewDataSource :

tableView:pasteboardWriterForRow:
tableView:validateDrop:proposedRow:proposedDropOperation:
tableView:acceptDrop:row:dropOperation:

Ecco un codice pienamente funzionante che supporta il trascinamento della selezione di più righe:

func tableView(tableView: NSTableView, pasteboardWriterForRow row: Int) -> NSPasteboardWriting? {
  let item = NSPasteboardItem()
  item.setString(String(row), forType: "public.data")
  return item
}

func tableView(tableView: NSTableView, validateDrop info: NSDraggingInfo, proposedRow row: Int, proposedDropOperation dropOperation: NSTableViewDropOperation) -> NSDragOperation {
  if dropOperation == .Above {
    return .Move
  }
  return .None
}

func tableView(tableView: NSTableView, acceptDrop info: NSDraggingInfo, row: Int, dropOperation: NSTableViewDropOperation) -> Bool {
  var oldIndexes = [Int]()
  info.enumerateDraggingItemsWithOptions([], forView: tableView, classes: [NSPasteboardItem.self], searchOptions: [:]) {
    if let str = ($0.0.item as! NSPasteboardItem).stringForType("public.data"), index = Int(str) {
            oldIndexes.append(index)
    }
  }

  var oldIndexOffset = 0
  var newIndexOffset = 0

  // For simplicity, the code below uses `tableView.moveRowAtIndex` to move rows around directly.
  // You may want to move rows in your content array and then call `tableView.reloadData()` instead.
  tableView.beginUpdates()
  for oldIndex in oldIndexes {
    if oldIndex < row {
      tableView.moveRowAtIndex(oldIndex + oldIndexOffset, toIndex: row - 1)
      --oldIndexOffset
    } else {
      tableView.moveRowAtIndex(oldIndex, toIndex: row + newIndexOffset)
      ++newIndexOffset
    }
  }
  tableView.endUpdates()

  return true
}

Versione Swift 3 :

func tableView(_ tableView: NSTableView, pasteboardWriterForRow row: Int) -> NSPasteboardWriting? {
    let item = NSPasteboardItem()
    item.setString(String(row), forType: "private.table-row")
    return item
}

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

func tableView(_ tableView: NSTableView, acceptDrop info: NSDraggingInfo, row: Int, dropOperation: NSTableViewDropOperation) -> Bool {
    var oldIndexes = [Int]()
    info.enumerateDraggingItems(options: [], for: tableView, classes: [NSPasteboardItem.self], searchOptions: [:]) {
        if let str = ($0.0.item as! NSPasteboardItem).string(forType: "private.table-row"), let index = Int(str) {
            oldIndexes.append(index)
        }
    }

    var oldIndexOffset = 0
    var newIndexOffset = 0

    // For simplicity, the code below uses `tableView.moveRowAtIndex` to move rows around directly.
    // You may want to move rows in your content array and then call `tableView.reloadData()` instead.
    tableView.beginUpdates()
    for oldIndex in oldIndexes {
        if oldIndex < row {
            tableView.moveRow(at: oldIndex + oldIndexOffset, to: row - 1)
            oldIndexOffset -= 1
        } else {
            tableView.moveRow(at: oldIndex, to: row + newIndexOffset)
            newIndexOffset += 1
        }
    }
    tableView.endUpdates()

    return true
}

Answer #4

Questo è un aggiornamento della risposta di @ Ethan per Swift 3:

let dragDropTypeId = "public.data" // or any other UTI you want/need

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

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

func tableView(_ tableView: NSTableView, acceptDrop info: NSDraggingInfo, row: Int, dropOperation: NSTableViewDropOperation) -> Bool {
    var oldIndexes = [Int]()
    info.enumerateDraggingItems(options: [], for: tableView, classes: [NSPasteboardItem.self], searchOptions: [:]) {
        if let str = ($0.0.item as! NSPasteboardItem).string(forType: self.dragDropTypeId), let index = Int(str) {
            oldIndexes.append(index)
        }
    }

    var oldIndexOffset = 0
    var newIndexOffset = 0

    // For simplicity, the code below uses `tableView.moveRowAtIndex` to move rows around directly.
    // You may want to move rows in your content array and then call `tableView.reloadData()` instead.
    tableView.beginUpdates()
    for oldIndex in oldIndexes {
        if oldIndex < row {
            tableView.moveRow(at: oldIndex + oldIndexOffset, to: row - 1)
            oldIndexOffset -= 1
        } else {
            tableView.moveRow(at: oldIndex, to: row + newIndexOffset)
            newIndexOffset += 1
        }
    }
    tableView.endUpdates()

    self.reloadDataIntoArrayController()

    return true
}




nstableview