views:

1160

answers:

2

I have a Core Data model which consists of a simple tree of a particular entity, which has two relationships, parent and children. I have an NSTreeController managing the model, with an NSOutlineView bound to the NSTreeController.

My problem is that I need a single root object, but this should not display in the outline view, only its children should be displayed at the top level of the outline view. If I set the fetch predicate of the NSTreeController in Interface Builder to parent == nil, everything works fine except the root item is visible as the top level item in the outline view.

My entity has an attribute, isRootItem, that is true for the root item only.

My model looks like this:

Node 1
|
+-Node 2
  |
  Node 3
  |   |
  |   +-Node 5
  |
  Node 4

The outline view should look like this:

Outline View Image

I need to display Nodes 2, 3 and 4 at the top level of the outline view (Node 1 should not be visible), but still have their parent be "Node 1". Node 1 has a value of YES for isRootItem and all the others have NO.

If I set the fetch predicate of the tree controller to parent.isRootItem == 1, this displays the tree correctly, but as soon as I add a new item to the top level it fails because the tree controller does not assign the "invisible" root item as the parent of the new item.

Is there a way to get the NSTreeController/NSOutlineView combination to work in this situation?

A: 

Have you tried binding to the NSTreeController's addChild: method?

It's because of times like this that I don't use NSTreeController.

You could do away with it and implement the delegate methods of the Source view which will give you much better control about what you display. It isn't too much extra work and might be easier than banging you head trying to get the NSTreeController to work how you want it to.

Abizern
That works for creating children of existing nodes. For example, if I selected "Node 2" and called `-addChild:` then a child will be correctly created. However, what I can't do is create new nodes at the same level as Node 2. If I call '-add:', a new node is created but its parent is nil, not my root item "Node 1".
Rob Keniger
If I can't get NSTreeController working the way I need then I'll definitely be reverting back to the datasource/delegate methods of NSOutlineView, but NSTreeController is so close to working that I want to try that path first.
Rob Keniger
It'll be interesting to see what other answers you get. I know what you mean about NSTreeController - sometimes it feels like it's almost there…
Abizern
+1  A: 

What I've ended up doing is subclassing NSTreeController and overriding -insertObject:atArrangedObjectIndexPath: to directly set the parent to my root object if the object being inserted is being inserted at the top level of the tree. This seems to work reliably.

Obviously more work would be needed to handle moving items and inserting multiple items but this seems to be the best way forward.

- (void)insertObject:(id)object atArrangedObjectIndexPath:(NSIndexPath *)indexPath
{
    NodeObject* item = (NodeObject*)object;
    //only add the parent if this item is at the top level of the tree in the outline view
    if([indexPath length] == 1)
    {
        //fetch the root item
        NSEntityDescription* entity = [NSEntityDescription entityForName:@"NodeObject" inManagedObjectContext:[self managedObjectContext]];
        NSFetchRequest* fetchRequest = [[NSFetchRequest alloc] init]; //I'm using GC so this is not a leak
        [fetchRequest setEntity:entity];
        NSPredicate* predicate = [NSPredicate predicateWithFormat:@"isRootItem == 1"];
        [fetchRequest setPredicate:predicate];

        NSError* error;
        NSArray* managedObjects = [[self managedObjectContext] executeFetchRequest:fetchRequest error:&error];

        if(!managedObjects)
        {
            [NSException raise:@"MyException" format:@"Error occurred during fetch: %@",error];
        }

        NodeObject* rootItem = nil;
        if([managedObjects count])
        {
            rootItem = [managedObjects objectAtIndex:0];
        }
        //set the item's parent to be the root item
        item.parent = rootItem;
    }
    [super insertObject:object atArrangedObjectIndexPath:indexPath];
    //this method just sorts the child objects in the tree so they maintain their order
    [self updateSortOrderOfModelObjects];
}
Rob Keniger