Miałem DUŻO żalu, próbując znaleźć sposób na zapisanie wyniku zmiany kolejności przeciągania i upuszczania przez użytkownika na moim NSTableView w danych podstawowych. Znalazłem w internecie kilka przydatnych informacji (np. to), ale z powodu mojej konfiguracji wiązania - sortDescriptors mojego tableview są powiązane z moim ArrayController w XCode Storyboard - stwierdziłem, że żadna z metod nie działa dla mnie. W nadziei, że może to pomóc komuś, kto przeżył tę samą frustrację, zamieszczam tutaj moje rozwiązanie.

0
Pixelboy 15 marzec 2020, 02:45

2 odpowiedzi

Najlepsza odpowiedź

Tylko wiersze między pierwszym i ostatnim przeciągniętym wierszem a wierszem upuszczenia wymagają ponownego zindeksowania. NSArrayController.rearrangeObjects() sortuje obiekty danych w nowej kolejności.

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 {

    if let items = billablesArrayController?.arrangedObjects as? [BillableItem] {

        NSAnimationContext.runAnimationGroup({(NSAnimationContext) -> Void in

            // put the dragged row indexes in an IndexSet so we can calculate which rows need moving and reindexing
            let rowArray = info.draggingPasteboard.pasteboardItems!.map{ Int($0.string(forType: .string)!)! }
            let draggedIndexes = IndexSet(rowArray)

            tableView.beginUpdates()

            // rows above drop row
            if draggedIndexes.first! < row {
                let indexesAboveDropRow = IndexSet(draggedIndexes.first! ..< row)

                // move the dragged rows down, start at the bottom to prevent the animated rows from tumbling over each other
                var newIndex = row - 1
                indexesAboveDropRow.intersection(draggedIndexes).reversed().forEach { oldIndex in
                    tableView.moveRow(at: oldIndex, to: newIndex)
                    items[oldIndex].sortOrder = Int16(newIndex)
                    newIndex -= 1
                }

                // reindex other rows
                indexesAboveDropRow.subtracting(draggedIndexes).reversed().forEach { oldIndex in
                    items[oldIndex].sortOrder = Int16(newIndex)
                    newIndex -= 1
                }
            }

            // rows below drop row
            if row < draggedIndexes.last! {
                let indexesBelowDropRow = IndexSet(row ... draggedIndexes.last!)

                // move the dragged rows up
                var newIndex = row
                indexesBelowDropRow.intersection(draggedIndexes).forEach { oldIndex in
                    tableView.moveRow(at: oldIndex, to: newIndex)
                    items[oldIndex].sortOrder = Int16(newIndex)
                    newIndex += 1
                }

                // reindex other rows
                indexesBelowDropRow.subtracting(draggedIndexes).forEach { oldIndex in
                    items[oldIndex].sortOrder = Int16(newIndex)
                    newIndex += 1
                }
            }

            tableView.endUpdates()

        }) {
            // rearrange the objects in the array controller so the objects match the moved rows
            // wait until the animation is finished to prevent weird or no animations
            self.billablesArrayController.rearrangeObjects()
        }

        // save
    }

    return true
}
1
Willeke 18 marzec 2020, 00:03

(UWAGA: ta metoda prawdopodobnie nie byłaby odpowiednia dla tableView z dużą liczbą wierszy, ponieważ przechodzimy przez wszystkie obiekty i ustawiamy nowe sortOrder)

Podsumowując problem - zmiana kolejności wyświetlania tabeli jest stosunkowo łatwa dzięki przydatnym postom SO, takim jak this - trudność polega na zapisaniu tych informacji w Core Data, ponieważ zmiana kolejności użytkowników / UI w tabeli jest nadpisywana przez sortDescriptors w powiązanym ArrayController. Powiązany ArrayController zasadniczo cofa zmianę kolejności wierszy tabeli użytkownika. Oto mój działający kod:

My arrayController sortDescriptors:

billablesArrayController.sortDescriptors = [NSSortDescriptor(key: "sortOrder", ascending: true)]

W moim onViewDidLoad of the ViewController:

override func viewDidLoad() {
    super.viewDidLoad()

    // Set ViewController as dataSource for tableView and register an array of accepted drag types
    billablesTableView.dataSource = self
    billablesTableView.registerForDraggedTypes([.string])
}

Zaimplementuj metody Drag and Drop w swoim ViewController:

extension JobsViewController: NSTableViewDataSource {

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

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

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

        if dropOperation == .above {
            // billablesArrayController.sortDescriptors are bound to tableView in xcode UI
            // so we remove arrayController sortDescriptors temporarily so as not to mess with user/UI table reordering
            billablesArrayController.sortDescriptors = []
            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: .string), let index = Int(str) {
                oldIndexes.append(index)
            }
        }

        var oldIndexOffset = 0
        var newIndexOffset = 0
        var selectionIndex = 0

        //Start tableView reordering

        tableView.beginUpdates()

        for oldIndex in oldIndexes {

            if oldIndex < row {
                tableView.moveRow(at: oldIndex + oldIndexOffset, to: row - 1)
                oldIndexOffset -= 1
                selectionIndex = row - 1
            } else {
                tableView.moveRow(at: oldIndex, to: row + newIndexOffset)
                newIndexOffset += 1
                selectionIndex = row
            }

        }

        tableView.endUpdates()

        //Get items.count from ArrayController for loop

        if let items = billablesArrayController?.arrangedObjects as? [BillableItem] {

            var newArray = [BillableItem]()

            // get the new item order from the tableView

            for i in 0..<items.count {

                if let view = billablesTableView.view(atColumn: 0, row: i, makeIfNecessary: false) as? NSTableCellView {
                    if let tableItem = view.objectValue as? BillableItem {
                        newArray.append(tableItem)
                    }
                }

            }

            // assign new sortOrder to each managedObject based on its index position in newArray
            var index = 0
            for bi in newArray {
                bi.sortOrder = Int16(index)
                index += 1
            }

        }

        // reinstate arrayController sortDescriptors
        billablesArrayController.sortDescriptors = [NSSortDescriptor(key: "sortOrder", ascending: true)]
        // assign the dragged row as the selected item
        billablesArrayController.setSelectionIndex(selectionIndex)

        //save 'em
        if managedObjectContext.hasChanges {

            do {

                try self.managedObjectContext.save()

            } catch {

                NSSound.beep()
                _ = alertDialog(question: "Error: Can't save billable items sort order.", text: error.localizedDescription, showCancel: false)

            }
        }

        return true

    }
}
0
Pixelboy 16 marzec 2020, 01:15