swift - 将 CollectionDifference 应用于 NSTableView
问题描述
对于这个例子,假设我CollectionDifference
从 annInt
数组生成一个,然后inferringMoves
像这样调用它
let a = [18, 18, 19, 11]
let b = [11, 19]
let diff = b.difference(from: a).inferringMoves()
for change in diff {
switch change {
case let .insert(offset, _, associatedWith):
if let from = associatedWith {
print("MOVE", from, offset)
} else {
print("INSERT", offset)
}
case let .remove(offset, _, associatedWith):
// If it is a MOVE it was already recorded in .insert
if associatedWith == nil {
print("REMOVE", offset)
}
}
}
现在我需要获取更改数组并将其提供给NSTableViews
更新方法
insertRows
removeRows
moveRow
以这种方式,它可以干净地应用。我的问题是move
条目的偏移量。上面的代码片段产生:
REMOVE 1
REMOVE 0
MOVE 2 1
现在显然我不能要求removeRows
and 0
,1
然后moveRow(2, 1)
,但这就是差异所暗示的。
我怎样才能干净地应用它?
问题似乎是NSTableView
在应用插入/删除时立即更新其内部计数,因此移动将不起作用。
解决方案
所以这比我最初想象的要复杂得多!
这是 CollectionDifference 的扩展,它将返回一组包含移动的步骤。我已经在各种复杂的序列上对此进行了测试,它看起来很可靠。
/*
This extension generates an array of steps that can be applied sequentially to an interface, or
associated collection, to remove, insert AND move items. Apart from the first and last steps, all
step indexes are transient and do not relate directly to the start or end collections.
This is complicated than it first appears and not something that I could reduced further. The
standard Changes are ordered: removals high->low, insertions low->high. Generating moves based on
insertions means that the associated removes are pulled out-of-order, which requires all the
later indexes to be offset in subtly different ways.
Delayed removals modify the insert indexes. Out of order removals, and insertions made before the
delayed removals modify the removal indexes. The effect of something that hasn't happeded yet, is
different to something that has happened but in the wrong order.
*/
extension CollectionDifference where ChangeElement: Hashable
{
public typealias Steps = Array<CollectionDifference<ChangeElement>.ChangeStep>
public enum ChangeStep {
case insert(_ element: ChangeElement, at: Int)
case remove(_ element: ChangeElement, at: Int)
case move(_ element: ChangeElement, from: Int, to: Int)
}
var maxOffset: Int { Swift.max(removals.last?.offset ?? 0, insertions.last?.offset ?? 0) }
public var steps: Steps {
guard !isEmpty else { return [] }
// A mapping to modify insertion indexees
let mapSize = maxOffset + count
var insertionMap = Array(0 ... mapSize)
// Items that may have been completed early relative to the Changes
var completeRemovals = Set<Int>()
var completeInsertions = Set<Int>()
var steps = Steps()
inferringMoves().forEach { change in
switch change {
case let .remove(offset, element, associatedWith):
if associatedWith != nil {
// Delayed removals can make step changes in insert locations
insertionMap.remove(at: offset)
} else {
steps.append(.remove(element, at: offset))
completeRemovals.insert(offset)
}
case let.insert(offset, element, associatedWith):
if let associatedWith = associatedWith
{
let from = associatedWith
- completeRemovals.filter({ $0 < associatedWith}).count
+ completeInsertions.filter({ $0 < associatedWith}).count
// Late removals re-adjust the insertion map by reducing higher indexes
insertionMap.indices.forEach {
if insertionMap[$0] >= associatedWith { insertionMap[$0] -= 1} }
let to = insertionMap[offset]
steps.append(.move(element, from: from, to: to))
completeRemovals.insert(associatedWith)
completeInsertions.insert(to)
} else {
let to = insertionMap[offset]
steps.append(.insert(element, at: to))
completeInsertions.insert(to)
}
}
}
return steps
}
}
extension CollectionDifference.Change
{
var offset: Int {
switch self {
case let .insert(offset, _, _): return offset
case let .remove(offset, _, _): return offset
}
}
}
这些步骤可以应用于 NSTableView 或 NSOutlineView,如下所示:
for step in updates {
switch step {
case let .remove(_, index):
outlineView.removeItems(at: [index], inParent: node, withAnimation: animation)
case let .insert(element, index):
outlineView.insertItems(at: [index], inParent: node, withAnimation: animation)
case let .move(element, from, to):
outlineView.moveItem(at: from, inParent: node, to: to, inParent: node)
}
}
推荐阅读
- python - 如何使用 bokeh.layouts.GridSpec?
- google-chrome - 制作 chrome/firefox 通知以保持更长时间
- python - 将 CSV 中的 ':' 替换为 ','
- yaml - YAML 解析器 yq 使用过滤器修改文件
- c++ - C++ 工具链和编译器有什么区别?
- json - 如何在液体 json 转换中的同一表达式中使用拆分和替换?
- javascript - 如何使用 jquery 向输入添加值而不是用新值替换输入
- python - 水波纹算法不能产生准确的结果
- python - 编写代码时显示此错误: ValueError: scatter requires x column to be numeric
- c# - 异常不会转发给父方法