Search code examples
objective-ccore-datamodel-view-controllernsoutlineviewnstreecontroller

Core Data, populating mandatory relationship from a newly created NSManagedObject


This should be easy, how difficult can it be.

I have a document based core data application with a very simple data model.

I have a "node" entity with a parent/children relationship to itself controlled by a NSTreeController, and viewed through an NSOutlineView. The "node" also has a non optional (to one) relationship to another entity type "nodeProperties" which is managed by a NSArrayController. I have NSManagedObject sublasses for both of the entities. My document class has outlets bound to both the tree controller and array controller instances.

My problem is how to ensure that, when a new "node" is created by a user interface action in the outline view, its relation to a suitable (pre-existing) nodeProperties object is populated.

Approaches I have tried / considered:

  1. Let the tree controller create the "node" (from its add:, addChild: actions) and populate the relationship to a nodeProperties object in the "node" subclass awakeFromInsert method. The trouble is I cannot find a means of accessing any nodeProperties object from within the "node"s awakeFromInsert. The "appropriate" nodeProperties object is available from a method in the document class, but accessing the document object from the node awakeFromInsert method seems to break the principles of MVC, and I have read that the shared document object is not always safe in a drag and drop operation (which in my case also creates a new node object)

  2. Write add: and addChild: action methods in the document class and invoke these from the end user actions instead of the tree controller (My drag and drop support is also in the document class). Then from within these methods invoke the add: and addChild: methods in the tree controller, then set the nodeProperties relationship on the newly created node. The trouble is I don't know how to ask the tree controller to give me a reference to the newly created node? I have tried using the selectedObjects method to get the parent, and then comparing the parents children before and after the add to get the new node. But the children content does not change at this time - perhaps it is a delayed update?

  3. As a variant of 2, don't use the tree controller add:/addChild: methods at all, but instead create the node entity object in the document add:/addChild: methods using the tree controllers selectedOjects to get the parent. I don't really like this since it seems like doing something behind the tree controllers back, and I would have to setContent: each time I created root objects.

  4. I have considered the possibility of observing the creation of the newly created node, but I don't know what to observe to achieve that.

Someone must have done something like this before - but I trawled to no avail. All help, advice, guidance would be very welcome.


Solution

  • OK so after much trawling and experimentation the answer was a variant of 3. The document creates the new node, populating its mandatory relationship, in add and addChild action methods, and then inserts the node into the tree controller using the method

    NSTreeController insertObject:atArrangedObjectIndexPath:

    For those interested, this is my addChild method in the document class. It has a few specifics from my data model

    - (IBAction)addChildAction:(id)sender
    {
        NSArray *indexPaths = [nodeTreeController selectionIndexPaths];
        NSArray *selectedObjects = [nodeTreeController selectedObjects];
        for (NSUInteger i = 0; i < [indexPaths count]; i++)
        {
            QVXpandNode *parentNode = [selectedObjects objectAtIndex:i];
            if ((parentNode) && ([parentNode.isMaster boolValue])) // can only add nodes under the master node
            {
                QVXpandNode *createdNode = [self createPopulatedNode];
                // Dont belelieve below is safe when >1 selected,
                // since adding a new node will result in the tree paths changing?
                // Hmmm but I do want to support multiple selection addition??
                [nodeTreeController
                    insertObject:createdNode
                    atArrangedObjectIndexPath:[[indexPaths
                        objectAtIndex:i] indexPathByAddingIndex:[parentNode.children count]]];
            }
        }
    }
    

    You will see that I am unsure whether I will put the second and later children at the right path if >1 rows were selected before calling the action.

    The addSibling method is slightly more complicated by the need to calculate the last index path value, but is otherwise similar. I can reproduce it if anyone wants to see it, but the key to populating a mandatory relationship in a new tree node is to do it in the document class and then tell the tree controller precisely where in the tree you want to insert it.