NSTreeController problems

  • Lately, I've been trying to update my stodgy old ways and try to
    incorporate some of the new technologies Cocoa has picked up over the
    years when starting new projects (my main app has had to be compatible
    with older OS X versions, so I haven't had much opportunity to jump
    into the world of bindings prior to now). One of these is
    NSTreeController. Unfortunately, I'm having a bit of trouble using
    figuring out how to do what I want with it.

    1. The order of the objects in my (non-Core Data) data model is
    sometimes important, so rather than using the tree controller to add
    objects, I have been adding them manually by calling the appropriate -
    insertObject:in<key>atIndex: methods. Okay, that works pretty well,
    and the objects show up in the NSOutlineView. However, I'd like to
    select the objects after I insert them. Now, I know where these
    objects are in the model, but since the order of the objects in the
    outline view might be different due to the user clicking on one column
    header or another to sort, and since the index paths sent to the tree
    controller's -setSelectedIndexPaths: method seem to be based on the
    interface, not the model, I don't know exactly where these objects are
    in order to select them. NSTreeController appears to have no -
    indexPathForObject: method or anything similar - does anyone know a
    way around this?

    1a. At first I thought that since I am making a sibling to the
    currently selected object, I could just get the parent's index path
    via [[treeController selectionIndexPath]
    indexPathByRemovingLastIndex], then get the node at that index path
    and iterate through its -childNodes until I find the one whose -
    representedObject is the correct model object, but there doesn't seem
    to be any way to get NSTreeController to give the node (or the model
    object, for that matter) for an index path. Does anyone else find this
    a little bizarre?

    2. Okay, so I've got my objects displaying in an NSOutlineView, and
    now I'd like to add a search feature. Rather than eliminating the
    options that don't match, what I want to do is find the object, expand
    all its ancestors in the outline view so it's visible, and select it.
    Finding the model object is easy, and doing the rest *would* be easy
    enough if I weren't using NSTreeController - just climb the family
    tree, use -rowForItem: for each, expand it, and then use -rowForItem:
    to get the object and select it. Of course, with NSTreeController we
    have all these NSTreeNode objects instead of the actual objects
    themselves in the outline view, so -rowForItem: won't work. Is there
    any way to find the rows for the various nodes in the family tree of
    an object without resorting to just iterating through every row in the
    outline view? Any way to get the tree node for a given model object?

    For me, the jury's still out - are these new bindings features really
    that useful, or is the best route just to pretend it's 2002 and
    continue to do things via the simpler method of making an outline
    delegate?

    Any assistance you can give is greatly appreciated.

    Charles
  • On 18 May 2008, at 20:57, Charles Srstka wrote:

    > Lately, I've been trying to update my stodgy old ways and try to
    > incorporate some of the new technologies Cocoa has picked up over
    > the years when starting new projects (my main app has had to be
    > compatible with older OS X versions, so I haven't had much
    > opportunity to jump into the world of bindings prior to now). One of
    > these is NSTreeController. Unfortunately, I'm having a bit of
    > trouble using figuring out how to do what I want with it.
    >
    > 1. The order of the objects in my (non-Core Data) data model is
    > sometimes important, so rather than using the tree controller to add
    > objects, I have been adding them manually by calling the appropriate
    > -insertObject:in<key>atIndex: methods. Okay, that works pretty well,
    > and the objects show up in the NSOutlineView. However, I'd like to
    > select the objects after I insert them. Now, I know where these
    > objects are in the model, but since the order of the objects in the
    > outline view might be different due to the user clicking on one
    > column header or another to sort, and since the index paths sent to
    > the tree controller's -setSelectedIndexPaths: method seem to be
    > based on the interface, not the model, I don't know exactly where
    > these objects are in order to select them. NSTreeController appears
    > to have no -indexPathForObject: method or anything similar - does
    > anyone know a way around this?
    >
    > 1a. At first I thought that since I am making a sibling to the
    > currently selected object, I could just get the parent's index path
    > via [[treeController selectionIndexPath]
    > indexPathByRemovingLastIndex], then get the node at that index path
    > and iterate through its -childNodes until I find the one whose -
    > representedObject is the correct model object, but there doesn't
    > seem to be any way to get NSTreeController to give the node (or the
    > model object, for that matter) for an index path. Does anyone else
    > find this a little bizarre

    To have an indexPathForObject method you need this:

    - (NSArray *)rootNodes;
    {
    return [[self arrangedObjects] childNodes];
    }

    // this is a depth-first search
    - (NSArray *)flattenedNodes;
    {
    NSMutableArray *mutableArray = [NSMutableArray array];
    for (NSTreeNode *node in [self rootNodes]) {
      [mutableArray addObject:node];
      if (![[node valueForKey:[self leafKeyPath]] boolValue])
      [mutableArray addObjectsFromArray:[node valueForKey:@"descendants"]];
    }
    return [[mutableArray copy] autorelease];
    }

    - (NSTreeNode *)treeNodeForObject:(id)object;
    {
    NSTreeNode *treeNode = nil;
    for (NSTreeNode *node in [self flattenedNodes]) {
      if ([node representedObject] == object) {
      treeNode = node;
      break;
      }
    }
    return treeNode;
    }

    - (NSIndexPath *)indexPathToObject:(id)object;
    {
    return [[self treeNodeForObject:object] indexPath];
    }

    They're all separate methods as I have quite a few extensions on
    NSTreeController!

    >
    > 2. Okay, so I've got my objects displaying in an NSOutlineView, and
    > now I'd like to add a search feature. Rather than eliminating the
    > options that don't match, what I want to do is find the object,
    > expand all its ancestors in the outline view so it's visible, and
    > select it. Finding the model object is easy, and doing the rest
    > *would* be easy enough if I weren't using NSTreeController - just
    > climb the family tree, use -rowForItem: for each, expand it, and
    > then use -rowForItem: to get the object and select it. Of course,
    > with NSTreeController we have all these NSTreeNode objects instead
    > of the actual objects themselves in the outline view, so -
    > rowForItem: won't work. Is there any way to find the rows for the
    > various nodes in the family tree of an object without resorting to
    > just iterating through every row in the outline view? Any way to get
    > the tree node for a given model object?

    Now you have a treeNode for object you can as the NSOutlineView to
    expand that item (the NSTreeNode) using -expandItem:

    Ive recently written two posts on NSTreeController, the first is non-
    Core Data the second is with core data but the sample project has a
    load of extensions that let you do what you want to do. They're at

    http://jonathandann.wordpress.com/2008/04/06/using-nstreecontroller/

    and

    http://jonathandann.wordpress.com/2008/05/13/nstreecontroller-and-core-data
    -sorted/


    Hope this helps

    Jon
  • Thanks, that looks very useful. I only have one trepidation about it,
    which concerns this:

    On May 18, 2008, at 3:33 PM, Jonathan Dann wrote:
    > - (NSArray *)rootNodes;
    > {
    > return [[self arrangedObjects] childNodes];
    > }

    Isn't -arrangedObjects one of those methods you're never supposed to
    call manually, only through bindings? At least, that seems to be the
    vibe I picked up while searching the list before posting my question.
    The docs say this:

    > Returns an proxy root tree node for the containing the receiver’s
    > sorted content objects.
    >
    > ...
    >

    > Prior to Mac OS X v10.5 this method returned an opaque root node
    > representing all the currently displayed objects. This method should
    > be used for binding, no assumption should be made about what methods
    > this object supports.

    which of course is nice and ambiguous. It says no assumption should be
    made, but says that in a paragraph prefixed by "Prior to Mac OS X
    v10.5". Since the root note was opaque and unreliable as to format
    prior to OS X v10.5, does that mean that it *can* be reliably used now
    that it is defined as an (sic) proxy root tree node for the (sic)
    containing the receiver's sorted content objects, or does that still
    apply?

    Thanks,
    Charles
  • On 18 May 2008, at 21:57, Charles Srstka wrote:

    > Thanks, that looks very useful. I only have one trepidation about
    > it, which concerns this:
    >
    > On May 18, 2008, at 3:33 PM, Jonathan Dann wrote:
    >> - (NSArray *)rootNodes;
    >> {
    >> return [[self arrangedObjects] childNodes];
    >> }
    >
    > Isn't -arrangedObjects one of those methods you're never supposed to
    > call manually, only through bindings? At least, that seems to be the
    > vibe I picked up while searching the list before posting my
    > question. The docs say this:
    >

    Not any more, the documentation may not have been updated yet.  The
    header for NSTreeController says differently as of 10.5.  It's always
    good to check the docs and the comments in the header file.  (control
    double click on the word NSTreeController in your code to get to the
    header). You often find the odd method that's not in the docs too.
    File a radar if you do for any of the classes you use.

    Glad to be of service, feel free to ask more.

    Jon
  • On May 18, 2008, at 5:47 PM, Jonathan Dann wrote:

    > Not any more, the documentation may not have been updated yet.  The
    > header for NSTreeController says differently as of 10.5.  It's
    > always good to check the docs and the comments in the header file.
    > (control double click on the word NSTreeController in your code to
    > get to the header). You often find the odd method that's not in the
    > docs too. File a radar if you do for any of the classes you use.
    >
    > Glad to be of service, feel free to ask more.
    >
    > Jon

    Oh sweet, you are indeed correct. The header also mentions a -
    descendantNodeAtIndexPath: method I can send this object that will
    allow me to get the node at a given index path, which was another
    thing I was having trouble doing.

    Thanks! :-)

    Charles
  • On 18 May 2008, at 23:57, Charles Srstka wrote:

    > On May 18, 2008, at 5:47 PM, Jonathan Dann wrote:
    >
    >> Not any more, the documentation may not have been updated yet.  The
    >> header for NSTreeController says differently as of 10.5.  It's
    >> always good to check the docs and the comments in the header file.
    >> (control double click on the word NSTreeController in your code to
    >> get to the header). You often find the odd method that's not in the
    >> docs too. File a radar if you do for any of the classes you use.
    >>
    >> Glad to be of service, feel free to ask more.
    >>
    >> Jon
    >
    > Oh sweet, you are indeed correct. The header also mentions a -
    > descendantNodeAtIndexPath: method I can send this object that will
    > allow me to get the node at a given index path, which was another
    > thing I was having trouble doing.
    >
    > Thanks! :-)
    >
    > Charles

    Yeah that one's really useful. NSTreeController is so much easier to
    use in 10.5 but those extensions to NSTreeController NSTreeNode and
    NSIndexPath make it really simple to use.

    You're welcome. These ones I didn't post but I use ALL the time too
    (note they call other methods in the sample project)

    //NSTreeNode_Extensions (insipred by Apple's SourceView sample code)
    - (NSArray *)descendants;
    {
    NSMutableArray *array = [NSMutableArray array];
    for (NSTreeNode *item in [self childNodes]) {
      [array addObject:item];
      if (![item isLeaf])
      [array addObjectsFromArray:[item descendants]];
    }
    return [[array copy] autorelease];
    }

    // NSTreeController_Extensions
    - (void)setSelectedNode:(NSTreeNode *)node;
    {
    [self setSelectionIndexPath:[node indexPath]];
    }

    - (void)setSelectedObject:(id)object;
    {
    [self setSelectedNode:[self treeNodeForObject:object]];
    }

    - (NSArray *)flattenedSelectedNodes;
    {
    NSMutableArray *mutableArray = [NSMutableArray array];
    for (NSTreeNode *treeNode in [self selectedNodes]) {
      if (![mutableArray containsObject:treeNode])
      [mutableArray addObject:treeNode];
      if (![[treeNode valueForKeyPath:[self leafKeyPath]] boolValue]) {
      [mutableArray addObjectsFromArray:[treeNode
    valueForKeyPath:@"descendants"]];
      }
    }
    return [[mutableArray copy] autorelease];
    }

    - (NSArray *)flattenedSelectedObjects;
    {
    return [[self flattenedSelectedNodes]
    valueForKey:@"representedObject"];
    }

    - (NSArray *)flattenedSelectedLeafNodes;
    {
    NSMutableArray *mutableArray = [NSMutableArray array];
    for (NSTreeNode *treeNode in [self flattenedSelectedNodes]) {
      if ([[treeNode valueForKeyPath:[self leafKeyPath]] boolValue])
      [mutableArray addObject:treeNode];
    }
    return [[mutableArray copy] autorelease];
    }

    - (NSArray *)flattenedSelectedLeafObjects;
    {
    return [[self flattenedSelectedLeafNodes]
    valueForKey:@"representedObject"];
    }

    - (NSArray *)flattenedSelectedGroupNodes;
    {
    NSMutableArray *mutableArray = [NSMutableArray array];
    for (NSTreeNode *treeNode in [self flattenedSelectedNodes]) {
      if (![[treeNode valueForKeyPath:[self leafKeyPath]] boolValue])
      [mutableArray addObject:treeNode];
    }
    return [[mutableArray copy] autorelease];
    }

    - (NSArray *)flattenedSelectedGroupObjects;
    {
    return [[self flattenedSelectedGroupNodes]
    valueForKey:@"representedObject"];
    }

    Jon
  • On May 18, 2008, at 6:44 PM, Jonathan Dann wrote:

    > Yeah that one's really useful. NSTreeController is so much easier to
    > use in 10.5 but those extensions to NSTreeController NSTreeNode and
    > NSIndexPath make it really simple to use.
    >
    > You're welcome. These ones I didn't post but I use ALL the time too
    > (note they call other methods in the sample project)

    Many thanks. :-)

    Charles