为什么具有 autosaveExpandedItems true 的基于视图的 NSOutlineView 忽略在 reloadData 时扩展?
Why View-Based NSOutlineView with autosaveExpandedItems true ignores expanded upon reloadData?
我使用自动保存展开状态的 NSOutlineView。如果我在数据源更新时手动重新加载数据,则不再调用 func outlineView(_ outlineView: NSOutlineView, itemForPersistentObject object: Any) -> Any?
数据源方法并且每个单元格都会崩溃。知道为什么会发生这种情况吗?
尝试使用 nil send 作为参数重新加载 Item,但仍然不行。
我用它来持久化展开的行:
func outlineView(_ outlineView: NSOutlineView, persistentObjectForItem item: Any?) -> Any? {
return NSKeyedArchiver.archivedData(withRootObject: item)
}
func outlineView(_ outlineView: NSOutlineView, itemForPersistentObject object: Any) -> Any? {
guard let data = object as? Data,
let item = NSKeyedUnarchiver.unarchiveObject(with: data) as? Category else { return nil }
let foundItem = recursiveSearch(for: item, in: viewModel.dataSource.value)
return foundItem
}
然后重新加载数据:
viewModel.dataSource.subscribe(onNext: { [weak self] _ in
self?.outlineView.reloadData()
}).disposed(by: disposeBag)
恕我直言,自动保存是一种 half-baked 功能,它无法按预期工作。换句话说,它的实现方式是在您的应用程序启动时(仅一次)恢复状态,然后您就可以自己了。
利用 outlineViewItemDidExpand(_:)
& outlineViewItemDidCollapse(_:)
实现您自己的一个(尤其是当我们重新加载时,...)。
如果您不想实现自定义自动保存,可以使用一些技巧。但我不会依赖他们。
第一个技巧 - 告诉 NSOutlineView 重新加载持久状态
NSOutlineView
继承自 NSTableView
和 autosaveName
属性 文档说:
If you change the value of this property to a new name, the table reads in any saved information and sets the order and width of this table view’s columns to match. Setting the name to nil
removes any previously stored state from the user defaults.
这里有什么不准确的地方 - 将其设置为 nil
不会删除之前为 NSOutlineView
存储的展开项目状态。我们可以用它来强制 NSOutlineView
重新加载展开的项目状态:
class ViewController: NSViewController, NSOutlineViewDelegate, NSOutlineViewDataSource {
@IBOutlet var outlineView: NSOutlineView!
// It's for testing, to demonstrate the persistent state reloading
private var doNotLoad = true
override func viewDidAppear() {
super.viewDidAppear()
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
self.doNotLoad = false
let autosaveName = self.outlineView.autosaveName
self.outlineView.autosaveName = nil
self.outlineView.reloadData()
self.outlineView.autosaveName = autosaveName
}
}
func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
if (doNotLoad) {
return 0
}
return item == nil ? data.count : (item as! Node).children.count
}
}
如果您想遵守文档,请不要使用 nil
并设置一些假名。但我希望一旦错误被修复,如果我们更改 autosaveName
或将其设置为 nil
.
,持久状态将被删除
第二招——加载和扩展自己
假设您有以下 Node
class:
class Node {
let id: Int
let children: [Node]
// ...
}
并且您的数据源实现了:
func outlineView(_ outlineView: NSOutlineView, persistentObjectForItem item: Any?) -> Any? {
(item as! Node).id
}
func outlineView(_ outlineView: NSOutlineView, itemForPersistentObject object: Any) -> Any? {
guard let id = object as? Int else { return nil }
return data.firstNode { [=12=].id == id }
}
firstNode
与这个问题无关,但是这里是实现(因为在代码中提到了):
extension Array where Self.Element == Node {
// Search for a node (recursively) until a matching element is found
func firstNode(where predicate: (Element) throws -> Bool) rethrows -> Element? {
for element in self {
if try predicate(element) {
return element
}
if let matched = try element.children.firstNode(where: predicate) {
return matched
}
}
return nil
}
}
然后你可以reloadData
& 自己展开所有项目:
outlineView.reloadData()
if outlineView.autosaveExpandedItems,
let autosaveName = outlineView.autosaveName,
let persistentObjects = UserDefaults.standard.array(forKey: "NSOutlineView Items \(autosaveName)"),
let itemIds = persistentObjects as? [Int] {
itemIds.forEach {
let item = outlineView.dataSource?.outlineView?(self.outlineView, itemForPersistentObject: [=14=])
self.outlineView.expandItem(item)
}
}
我使用自动保存展开状态的 NSOutlineView。如果我在数据源更新时手动重新加载数据,则不再调用 func outlineView(_ outlineView: NSOutlineView, itemForPersistentObject object: Any) -> Any?
数据源方法并且每个单元格都会崩溃。知道为什么会发生这种情况吗?
尝试使用 nil send 作为参数重新加载 Item,但仍然不行。
我用它来持久化展开的行:
func outlineView(_ outlineView: NSOutlineView, persistentObjectForItem item: Any?) -> Any? {
return NSKeyedArchiver.archivedData(withRootObject: item)
}
func outlineView(_ outlineView: NSOutlineView, itemForPersistentObject object: Any) -> Any? {
guard let data = object as? Data,
let item = NSKeyedUnarchiver.unarchiveObject(with: data) as? Category else { return nil }
let foundItem = recursiveSearch(for: item, in: viewModel.dataSource.value)
return foundItem
}
然后重新加载数据:
viewModel.dataSource.subscribe(onNext: { [weak self] _ in
self?.outlineView.reloadData()
}).disposed(by: disposeBag)
恕我直言,自动保存是一种 half-baked 功能,它无法按预期工作。换句话说,它的实现方式是在您的应用程序启动时(仅一次)恢复状态,然后您就可以自己了。
利用 outlineViewItemDidExpand(_:)
& outlineViewItemDidCollapse(_:)
实现您自己的一个(尤其是当我们重新加载时,...)。
如果您不想实现自定义自动保存,可以使用一些技巧。但我不会依赖他们。
第一个技巧 - 告诉 NSOutlineView 重新加载持久状态
NSOutlineView
继承自 NSTableView
和 autosaveName
属性 文档说:
If you change the value of this property to a new name, the table reads in any saved information and sets the order and width of this table view’s columns to match. Setting the name to
nil
removes any previously stored state from the user defaults.
这里有什么不准确的地方 - 将其设置为 nil
不会删除之前为 NSOutlineView
存储的展开项目状态。我们可以用它来强制 NSOutlineView
重新加载展开的项目状态:
class ViewController: NSViewController, NSOutlineViewDelegate, NSOutlineViewDataSource {
@IBOutlet var outlineView: NSOutlineView!
// It's for testing, to demonstrate the persistent state reloading
private var doNotLoad = true
override func viewDidAppear() {
super.viewDidAppear()
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
self.doNotLoad = false
let autosaveName = self.outlineView.autosaveName
self.outlineView.autosaveName = nil
self.outlineView.reloadData()
self.outlineView.autosaveName = autosaveName
}
}
func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
if (doNotLoad) {
return 0
}
return item == nil ? data.count : (item as! Node).children.count
}
}
如果您想遵守文档,请不要使用 nil
并设置一些假名。但我希望一旦错误被修复,如果我们更改 autosaveName
或将其设置为 nil
.
第二招——加载和扩展自己
假设您有以下 Node
class:
class Node {
let id: Int
let children: [Node]
// ...
}
并且您的数据源实现了:
func outlineView(_ outlineView: NSOutlineView, persistentObjectForItem item: Any?) -> Any? {
(item as! Node).id
}
func outlineView(_ outlineView: NSOutlineView, itemForPersistentObject object: Any) -> Any? {
guard let id = object as? Int else { return nil }
return data.firstNode { [=12=].id == id }
}
firstNode
与这个问题无关,但是这里是实现(因为在代码中提到了):
extension Array where Self.Element == Node {
// Search for a node (recursively) until a matching element is found
func firstNode(where predicate: (Element) throws -> Bool) rethrows -> Element? {
for element in self {
if try predicate(element) {
return element
}
if let matched = try element.children.firstNode(where: predicate) {
return matched
}
}
return nil
}
}
然后你可以reloadData
& 自己展开所有项目:
outlineView.reloadData()
if outlineView.autosaveExpandedItems,
let autosaveName = outlineView.autosaveName,
let persistentObjects = UserDefaults.standard.array(forKey: "NSOutlineView Items \(autosaveName)"),
let itemIds = persistentObjects as? [Int] {
itemIds.forEach {
let item = outlineView.dataSource?.outlineView?(self.outlineView, itemForPersistentObject: [=14=])
self.outlineView.expandItem(item)
}
}