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


