Перетащите и измените порядок строк в NSTableView - PullRequest
16 голосов
/ 23 января 2010

Мне было просто интересно, есть ли простой способ установить NSTableView, чтобы он мог переупорядочивать свои строки без написания кода для монтажа. Мне это нужно только для того, чтобы сделать это внутренне, за одним столом. У меня нет проблем с написанием кода для pboard, за исключением того, что я вполне уверен, что видел, как Interface Builder где-то это переключал / видел, что он работает по умолчанию Это, безусловно, кажется достаточно общей задачей.

Спасибо

Ответы [ 7 ]

27 голосов
/ 11 ноября 2014

Установите в качестве источника данных табличного представления класс, соответствующий NSTableViewDataSource.

Поместите это в подходящее место (-applicationWillFinishLaunching, -awakeFromNib, -viewDidLoad или что-то подобное):

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

Затем реализуйте эти три NSTableViewDataSource метода:

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

Вот полностью рабочий код, поддерживающий переупорядочение нескольких строк методом перетаскивания:

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
}

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
}
14 голосов
/ 26 января 2010

Если вы посмотрите на подсказку в IB, вы увидите, что опция, к которой вы обращаетесь

- (BOOL)allowsColumnReordering

контролирует, ну, перестановка столбцов. Я не верю, что существует какой-либо другой способ сделать это, кроме стандартного API-интерфейса перетаскивания для табличных представлений .

РЕДАКТИРОВАТЬ: (2012-11-25)

Ответ относится к переупорядочению с помощью перетаскивания NSTableViewColumns; и в то время это был принятый ответ в то время. Похоже, сейчас, спустя почти 3 года, это не так. Чтобы сделать информацию полезной для искателей, я постараюсь дать более правильный ответ.

В Интерфейсном Разработчике нет параметра, позволяющего перетаскивать и переупорядочивать строки NSTableView. Вам необходимо реализовать определенные NSTableViewDataSource методы, в том числе:

- tableView:acceptDrop:row:dropOperation:

- (NSDragOperation)tableView:(NSTableView *)aTableView validateDrop:(id < NSDraggingInfo >)info proposedRow:(NSInteger)row proposedDropOperation:(NSTableViewDropOperation)operation

- (BOOL)tableView:(NSTableView *)aTableView writeRowsWithIndexes:(NSIndexSet *)rowIndexes toPasteboard:(NSPasteboard *)pboard

Существуют и другие вопросы, которые касаются этого достаточно подробно, включая этот

8 голосов
/ 17 сентября 2018

@ Решение Итана - Обновление Swift 4

в viewDidLoad:

private var dragDropType = NSPasteboard.PasteboardType(rawValue: "private.table-row")

override func viewDidLoad() {
    super.viewDidLoad()

    myTableView.delegate = self
    myTableView.dataSource = self
    myTableView.registerForDraggedTypes([dragDropType])
}

Позже расширение делегата:

extension MyViewController: NSTableViewDelegate, NSTableViewDataSource {

    // numerbOfRow and viewForTableColumn methods

    func tableView(_ tableView: NSTableView, pasteboardWriterForRow row: Int) -> NSPasteboardWriting? {

        let item = NSPasteboardItem()
        item.setString(String(row), forType: self.dragDropType)
        return item
    }

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

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

    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.dragDropType), 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
    }

}

Плюс, для тех, кто может согласиться:

  1. Если вы хотите отключить некоторые ячейки от перетаскивания, верните nil в методе pasteboardWriterForRow s

  2. Если вы хотите предотвратить падение определенных мест (например, слишком далеко), просто используйте return [] в методе validateDrop

  3. Не вызывайте tableView.reloadData () синхронно внутри func tableView(_ tableView:, acceptDrop info:, row:, dropOperation:). Это нарушит анимацию перетаскивания и может привести к путанице. Найдите способ дождаться окончания анимации и выполните асинхронную перезагрузку

5 голосов
/ 28 декабря 2016

Этот ответ распространяется на Swift 3 , на основе вида NSTableView s и переупорядочение с одной и несколькими строками.

Для достижения этой цели необходимо выполнить 2 основных шага:

  • Регистрация табличного представления для специально разрешенного типа объекта, который можно перетаскивать.

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

  • Реализация 3 NSTableViewDataSource методов: writeRowsWith, validateDrop и acceptDrop.

Перед началом операции перетаскивания сохраните 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
    }

Проверять удаление, только если операция перетаскивания находится выше указанной строки. Это гарантирует, что при перетаскивании другие строки не будут выделены, когда перетаскиваемый ряд будет находиться над ними. Кроме того, это устраняет проблему AutoLayout.

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

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

При приеме удаления просто получите IndexSet, который ранее был сохранен в монтажном столе, перебирайте и перемещайте строки, используя рассчитанные индексы. Примечание: часть с итерацией и перемещением строк, которую я скопировал из ответа @ Итана.

    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
    }

Обновление NSTableView на основе просмотра при вызове moveRow не требует использования блоков beginUpdates() и endUpdates().

4 голосов
/ 24 октября 2013

К сожалению, вы должны написать код доски вставки. API Drag and Drop довольно универсален, что делает его очень гибким. Тем не менее, если вам просто нужно изменить порядок, это немного чрезмерно ИМХО. Но в любом случае я создал небольшой пример проекта , в котором есть NSOutlineView, где вы можете добавлять и удалять элементы, а также изменять их порядок.

Это не NSTableView, но реализация протокола Drag & Drop в основном идентична.

Я реализовал перетаскивание за один раз, поэтому лучше взглянуть на этот коммит .

screenshot

3 голосов
/ 06 января 2017

Если вы перемещаете только одну строку за раз, вы можете использовать следующий код:

    func tableView(_ tableView: NSTableView, acceptDrop info: NSDraggingInfo, row: Int, dropOperation: NSTableViewDropOperation) -> Bool {
        let pasteboard = info.draggingPasteboard()
        guard let pasteboardData = pasteboard.data(forType: basicTableViewDragAndDropDataType) else { return false }
        guard let rowIndexes = NSKeyedUnarchiver.unarchiveObject(with: pasteboardData) as? IndexSet else { return false }
        guard let oldIndex = rowIndexes.first else { return false }

        let newIndex = oldIndex < row ? row - 1 : row
        tableView.moveRow(at: oldIndex, to: newIndex)

        // Dont' forget to update model

        return true
    }
0 голосов
/ 06 октября 2016

Это обновление к @ ответу Итана для 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
}
...