NSOutlineView的自动保存已展开项目功能无法正常工作

8
我正在尝试使用"自动保存展开项"功能。当我展开一个带有子项的分组并重新启动应用程序时,所有子项都会再次折叠,我不知道为什么它们不能保持展开状态。我使用core data来存储我的源列表项。
到目前为止,这是我所做/设置的:
- 在NSOutlineView(源列表)中选中了"自动保存展开项"。 - 为"自动保存"设置了名称。 - dataSource和delegate outle赋值给我的控制器。
这是我的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];
}

有任何想法吗?谢谢。

编辑: 我有线索!问题可能是树控制器没有及时准备其内容。在数据加载完成之前或者NSOutlineView尚未完成初始化之前,applicationDidFinishLaunching、outlineView:persistentObjectForItem等方法将被执行。有什么解决办法吗?


2
你找到解决方案了吗?我有一个类似的问题,尽管我不使用CoreData而是使用绑定。实际上,方法outlineView:itemForPersistentObject:在应用程序完成启动之前被调用。 - onekiloparsec
7个回答

3

我曾经遇到一个问题,我的-outlineView:itemForPersistentObject:实现根本没有被调用。事实证明,只有在设置了“autosaveExpandedItems”或“autosaveName”之后才会调用此方法。 我的解决方案是在代码中设置这两个属性,而不是在InterfaceBuilder中设置。当我在分配委托之后设置这些属性时,该方法被调用。


2

我已经让它工作 - 你需要返回相应的树节点而不是“仅仅”它所代表的对象。

itemForPersistentObject:中,你需要使用return [self itemForObject:item inNodes:[_treeController.arrangedObjects childNodes]];来代替return item;

带着这个修改:

- (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实例。


1

哇!6年过去了,这仍然是个头疼的问题。

一开始我无法使其工作,即使有Karsten提供的有用解决方案来设置代码中的autoSaveName和autosaveExpandedItems;itemForPersistentObject仍然在outlineView填充之前被调用。对我而言,虽然不太优雅,但解决方案是在设置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
}

你好。我有同样的问题。当在IB中设置autosavename属性时,会在填充树控制器之前调用itemForPersistentObject:,这很愚蠢(我不确定为什么appkit不检查这个)。我将autosaveName属性设置为在树控制器的content属性更改时触发调用(使用观察者)。这有效。 - jeanlain

1

对Karsten的解决方案进行扩展:

在执行Karsten建议的操作后,方法-outlineView:itemForPersistentObject:会被调用,但前提是在设置委托之前也设置了数据源。

因此,如果Karsten的答案似乎无效,请检查您的数据源设置位置并进行相应调整。

(想将此写为评论,但由于我是新手而不允许...)


1

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
}

如果您正在使用树控制器,那么如何使其工作?我必须将ViewController设置为数据源,但它会抱怨所有其他缺失的函数,例如numberOfChildrenOfItem等等,这些函数可能是从treeController获取的。 - Duncan Groenewald
@DuncanGroenewald,我不使用树控制器,因为我遇到了太多问题。使用数据源和委托会更加灵活。 - vomi
@vomi 看起来很不错,但我似乎找不到managedObjectContextpersistentStoreCoordinator。不过我找到了NSManagedObjectContextNSPersistentStoreCoordinator... 你能给我一些提示吗? - RoyRao

0

我从来没有让这个工作起来。

这是我目前的做法:

首先,我添加了一个属性“isExpanded”,并在数据库中保存每个节点的状态。

enter image description here

其次,当我的treeController准备好其内容时,我会展开节点。
- (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"];
    }
}

2
依我之见,这是一件非常糟糕的事情。将isExpanded属性添加到您的模型对象中只会混淆模型和用户界面,并破坏了模型-视图-控制器模式。如果明天你决定将你的大纲视图更改为表格或集合视图,会发生什么?这个属性变得无用了。如果你决定让你的对象通过2个不同的大纲视图展示会怎样?你会添加一个属性吗?此外,如果你将你的模型保存到云端,每当用户展开/折叠一个项目时,你都会产生网络流量?非常糟糕... - AirXygène

0
我通过在Interface Builder中设置autosaveName和在viewDidAppear代码中设置autosaveExpandedItems来解决了这个问题:
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
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接