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.
2 odpowiedzi
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
}
(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
}
}
Podobne pytania
Powiązane pytania
Nowe pytania
swift
Swift to bezpieczny, szybki i ekspresyjny język programowania ogólnego przeznaczenia opracowany przez Apple Inc. dla platform i Linux. Swift jest źródłem open. Użyj tagu tylko dla pytań dotyczących funkcji językowych lub wymagających kodu w Swift. Użyj tagów [iOS], [iPados], [MacOS], [Watch-OS], [TVOS], [Swiftui], [Cocoa-Touch] i [Cocoa] dla (język-agnostyczne) pytania o platformach lub ramy.