【问题标题】:Saving NSTableView Reordering in Core Data with NSArrayController Binding使用 NSArrayController 绑定在核心数据中保存 NSTableView 重新排序
【发布时间】:2020-03-14 23:45:33
【问题描述】:

在试图找到一种方法将用户在我的 NSTableView 上拖放重新排序的结果保存到 Core Data 中时,我感到非常难过。我在网上找到了一些有用的点点滴滴(如this),但由于我的绑定设置——我的tableview 的sortDescriptors 绑定到我在XCode Storyboard 中的ArrayController——我发现没有一个方法对我有用。希望这可以帮助遭受同样挫折的其他人,我在这里发布我的解决方案。

【问题讨论】:

    标签: swift xcode macos


    【解决方案1】:

    只有第一个和最后一个拖动行和放置行之间的行需要重新索引。 NSArrayController.rearrangeObjects() 将数据对象按新顺序排序。

    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
    }
    

    【讨论】:

      【解决方案2】:

      (注意:此方法可能不适用于具有大量行数的 tableView,因为我们正在遍历所有对象并设置新的 sortOrder)

      总结这个问题 - 让 tableview 重新排序工作相对容易,这要归功于this 等有用的 SO 帖子 - 困难在于将此信息保存到 Core Data,因为表的用户/UI 重新排序被覆盖您绑定的 ArrayController 上的 sortDescriptors。绑定的 ArrayController 实质上撤消了用户的表行重新排序。这是我的工作代码:

      我的数组控制器 sortDescriptors:

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

      在我的 ViewController 的 onViewDidLoad 中:

      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])
      }
      

      在 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
      
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-06-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多