0

I have an NSOutlineView populated from an NSOutlineViewDataSource which is a thin wrapper over a supplied object model, containing a mixture of arrays, dictionaries and primitive types.

However the object model has loops in its hierarchy. I thought that this shouldn't be a problem, as a user would not expand that node (or get bored after a while) but the two different paths refer to the same object instance, what happens to one happens to the other, if the parent is expanded (as it would be) then the child item is also expanded resulting in an infinite loop.

I've tried putting conditional logic into my DataSource, to not return an object if the document dictionary key is "Parent". But all I see in isItemExpandable is a reference to the object, and I have no idea if the key is "Parent" or not.

I've tried caching the objects in an NSDictionary, with their keys, to see if I've encountered them before, this allowed me to determine the key name and return NO for isItemExpandable this partially worked but as they are same object, the parent's object key was overwritten with "Parent" changing the name shown in the NSOutlineView and preventing the "Parent" key from being expanded or collapsed.

The datasource is populated via callbacks, I haven't got much context to determine if the object is the parent node, or the child, let alone if it is referenced multiple times.

I've seen a similar questions refer to NSPathIndex but that appears to be for array indexes and not dictionary keys, NSTreeNode has a similar problem and there doesn't appear to be an NSKeyPath class.

Does anyone know how to handle this case?

silicontrip
  • 906
  • 7
  • 23
  • Is the data mutable? Is `NSTreeController` and Cocoa Bindings an option? – Willeke Jan 11 '21 at 07:02
  • I'm not familiar with Cocoa bindings, the model is a 3rd party c++ class, which I've written a thin obj-c wrapper over. Currently the data isn't mutable but there are plans to enable editing in this application. – silicontrip Jan 14 '21 at 04:56
  • I've managed to work around it by walking up the outline tree using`NSOutlineView parentForItem:` if the item requested by `NSOutlineView:child:ofItem:` matches any of the objects in the path to the root node, I return a sentinel value (a pointer to an object holding a null) I'd prefer it to return the actual object and let the user determine if they should descend into it but this cuts off any links back up the tree causing infinite loops. – silicontrip Jan 14 '21 at 23:42
  • Wrap every object in a `NSTreeNode` and repeated objects in new `NSTreeNode`s. I'll post an answer, is the root a node or an array? Objective-C or Swift? – Willeke Jan 15 '21 at 09:56
  • I'm using Objective-c and wrapping the c++ class pointer in an `NSValue` and storing them as the item in the `NSOutlineView` object, since it's a new instance of `NSValue`, the outlineview must be using a concrete class specific comparitor. I did encounter `NSTreeNode` but it looked more like a read only class. I now see `mutableChildNodes` to build the tree, just how does it work with a Dictionary? I've also seen a preference for `NSTreeController' or can I still use my `NSOutlineViewDataSource`? Oh and the root node is a dictionary. Thanks. – silicontrip Jan 16 '21 at 04:01

1 Answers1

0

Each item in the outline view must be unique. Wrap each node in a NSTreeNode or similar custom class. Repeated nodes are wrapped again. For example:

MyObject is a NSObject subclass with property children but representedObject can be anything.

@property NSTreeNode *rootNode;

// setup
self.rootNode = [[NSTreeNode alloc] initWithRepresentedObject:rootObject];
[self.outlineView reloadData];

- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(NSTreeNode *)item {
    if (!item)
        item = self.rootNode;
    MyObject *object = item.representedObject;
    return object.children.count;
}

- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(NSTreeNode *)item {
    if (!item)
        item = self.rootNode;
    MyObject *object = item.representedObject;
    /*
    // show repeated nodes as leaves
    NSTreeNode *parentItem = item.parentNode;
    while (parentItem) {
        if (parentItem.representedObject == object)
            return NO;
        parentItem = parentItem.parentNode;
    }
    */
    return object.children.count > 0;
}

- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(NSTreeNode *)item {
    if (!item)
        item = self.rootNode;
    if (index >= item.childNodes.count) {
        // create child nodes
        NSMutableArray *childrenArray = item.mutableChildNodes;
        [childrenArray removeAllObjects];
        MyObject *object = item.representedObject;
        for (MyObject *childObject in object.children)
            [childrenArray addObject:[[NSTreeNode alloc] initWithRepresentedObject:childObject]];
    }
    return item.childNodes[index];
}
Willeke
  • 14,578
  • 4
  • 19
  • 47
  • I had to change model libraries therefor re-wrote my DataSource class, where I discovered a bug which would've caused my earlier issue. I apologise for that. However this answer did help me solve the next issue I encountered, trying to find the element in the parent node representing this node. – silicontrip Jan 26 '21 at 00:14