我正在尝试使用“自动保存扩展项目”功能。当我展开一个组及其子项并重新启动应用程序时,所有子项都会再次折叠,我不知道为什么它们不会保持展开状态。 我正在使用核心数据来存储我的源列表项。
这是我到目前为止所做/设置的:
这是我对outlineView:persistentObjectForItem 和outlineView:itemForPersistentObject 的实现。
- (id)outlineView:(NSOutlineView *)anOutlineView itemForPersistentObject:(id)object
{
NSURL *objectURI = [[NSURL alloc] initWithString:(NSString *)object];
NSManagedObjectID *mObjectID = [_persistentStoreCoordinator managedObjectIDForURIRepresentation:objectURI];
NSManagedObject *item = [_managedObjectContext existingObjectWithID:mObjectID error:nil];
return item;
}
- (id)outlineView:(NSOutlineView *)anOutlineView persistentObjectForItem:(id)item
{
NSManagedObject *object = [item representedObject];
NSManagedObjectID *objectID = [object objectID];
return [[objectID URIRepresentation] absoluteString];
}
有什么想法吗?谢谢。
编辑: 我有线索了!问题可能是树控制器没有按时准备其内容。 applicationDidFinishLaunching、outlineView:persistentObjectForItem 等方法在数据加载之前执行,或者 NSOutlineView 尚未完成初始化。有什么想法如何解决这个问题吗?
我遇到的问题是我的 -outlineView:itemForPersistentObject: 实现根本没有被调用。事实证明,当设置“autosaveExpandedItems”或“autosaveName”时,会调用此方法。 我的解决方案是在代码中设置这两个属性,而不是在 InterfaceBuilder 中设置。当我在分配委托后设置属性时,该方法将被调用。
我让它工作 - 你需要返回相应的树节点,而不是“仅仅”它所代表的对象。
在
itemForPersistentObject:
中,您需要 return item;
而不是
return [self itemForObject:item inNodes:[_treeController.arrangedObjects childNodes]];
与
- (id)itemForObject:(id)object inNodes:(NSArray *)nodes {
for (NSTreeNode *node in nodes) {
if ([node representedObject] == object)
return node;
id item = [self itemForObject:object inNodes:node.childNodes];
if (item)
return item;
}
return nil;
}
其中
_treeController
是用于填充大纲视图的 NSTreeController
实例。
扩展 Karsten 的解决方案:
方法
-outlineView:itemForPersistentObject:
在执行 Karsten 建议的操作后被调用,但前提是您在设置委托之前还设置了数据源。
因此,如果 Karsten 的答案似乎不起作用,请检查数据源的设置位置并进行相应调整。
(想写这个作为评论,但由于我的新手身份,我不被允许......)
哇! 6年过去了,这仍然令人头痛。
即使使用 Karsten 的有用解决方案在代码中重新设置 autoSaveName 和 autosaveExpandedItems,我最初也无法使其正常工作;在填充outlineView 之前,itemForPersistentObject 仍在被调用。对我来说,解决方案虽然不是很优雅,但在设置 autosaveExpandedItems 和 autoSaveName 之前设置 0.5 秒的延迟。我的应用程序中的半秒延迟并不明显。我也使用了Vomi的代码。委托和数据源在 IB 绑定中设置。这是完整的解决方案:
override func viewDidLoad() {
super.viewDidLoad()
let _ = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false) { (timer) in
self.keywordsOutlineView.autosaveExpandedItems = true
self.keywordsOutlineView.autosaveName = "KeywordsOutlineView"
timer.invalidate()
}
}
func outlineView(_ outlineView: NSOutlineView, persistentObjectForItem item: Any?) -> Any? {
if let node = item as? NSTreeNode {
if let object = node.representedObject as? FTKeyword {
return object.objectID.uriRepresentation().absoluteString
}
}
return nil
}
// This method should return a NSTreeNode object
func outlineView(_ outlineView: NSOutlineView, itemForPersistentObject object: Any) -> Any? {
if outlineView == keywordsOutlineView {
guard let uriAsString = object as? String,
let uri = URL(string: uriAsString) else { return nil }
if let psc = self.managedObjectContext.persistentStoreCoordinator,
let moID = psc.managedObjectID(forURIRepresentation: uri),
let group = self.managedObjectContext.object(with: moID) as? FTKeyword,
let nodes = self.keywordsTreeController.arrangedObjects.children {
return self.findNode(for: group, in: nodes)
}
return nil
}
return nil
}
/// Utility method to find the corresponding NSTreeNode for a given represented object
private func findNode(for object: NSManagedObject, in nodes: [NSTreeNode]) -> NSTreeNode? {
for treeNode in nodes {
if (treeNode.representedObject as? NSManagedObject) === object {
return treeNode
}
}
return nil
}
Swift 5 答案
Karsten 是对的,
itemForPersistentObject
必须返回一个 NSTreeNode。
这是解决方案的 Swift 5 版本:
// This method should return a NSTreeNode object
func outlineView(_ outlineView: NSOutlineView, itemForPersistentObject object: Any) -> Any? {
guard let uriAsString = object as? String,
let uri = URL(string: uriAsString) else { return nil }
if let psc = self.managedObjectContext.persistentStoreCoordinator,
let moID = psc.managedObjectID(forURIRepresentation: uri),
let group = self.managedObjectContext.object(with: moID) as? MyGroupEntity,
let nodes = self.expensesTreeController.arrangedObjects.children {
return self.findNode(for: group, in: nodes)
}
return nil
}
/// Utility method to find the corresponding NSTreeNode for a given represented object
private func findNode(for object: NSManagedObject, in nodes: [NSTreeNode]) -> NSTreeNode? {
for treeNode in nodes {
if (treeNode.representedObject as? NSManagedObject) === object {
return treeNode
}
}
return nil
}
我通过在 Interface Builder 中设置
autosaveName
并在 autosaveExpandedItems
中的代码中设置 viewDidAppear
解决了这个问题:
override func viewDidAppear() {
super.viewDidAppear()
outlineView.autosaveExpandedItems = true
}
对于我的简单情况,当
item
只是[Int]
时,这些persistentObjectForItem:
和itemForPersistentObject
起作用(我没有阅读返回NSTreeNode
):
func outlineView(_ outlineView: NSOutlineView, persistentObjectForItem item: Any?) -> Any? {
return item
}
func outlineView(_ outlineView: NSOutlineView, itemForPersistentObject object: Any) -> Any? {
return object
}
我从来没有成功过。
这是我目前的做法:
首先,我添加了一个属性“isExpanded”,并将每个节点的状态保存在数据库中。
其次,当我的树控制器准备好其内容时,我展开节点。
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
[treeSectionController addObserver:self
forKeyPath:@"content"
options:0
context:nil];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (object == treeSectionController) {
NSArray *sectionArray = [[treeSectionController arrangedObjects] childNodes];
for (NSTreeNode *node in sectionArray) {
if([[node representedObject] isExpandedValue]) {
[outlinePilesView expandItem:node];
}
}
[treeSectionController removeObserver:self forKeyPath:@"content"];
}
}
等待NSTreeController的arrangedObjects被初始化。
var treeContentObserver: NSKeyValueObservation?
override func viewDidLoad() {
super.viewDidLoad()
// setup outlineview, treeController, bind content, etc.
treeContentObserver = treeController.observe(\.arrangedObjects, options: [.initial]) { (_, _) in
Task {
await MainActor.run {
self.outlineView.autosaveExpandedItems = true
self.outlineView.autosaveName = "uniqueNameForThisOutlineView"
if let observer = self.treeContentObserver {
observer.invalidate()
self.treeContentObserver = nil
}
}
}
}
}