Cocoa and Objective-C: Up and Running (by me) is now available from O'Reilly.

NSTreeController and Drag and Drop

I've posted enough about this to cocoa-dev that I think it's time to actually write it down. In Tiger, using NSTreeController with NSOutlineView means drag-and-drop doesn't work as expected. This definitely does not mean you should abandon bindings, especially if you're using Core Data. There are two key things you need to know.

One: Unlike NSTableView, NSOutlineView expects you to implement all of the "required" datasource methods if you plan to use any of them. The simple solution is to provide dummy implementations:

#pragma mark -
#pragma mark NSOutlineView Hacks for Drag and Drop

- (BOOL) outlineView: (NSOutlineView *)ov
         isItemExpandable: (id)item { return NO; }

- (int)  outlineView: (NSOutlineView *)ov
         numberOfChildrenOfItem:(id)item { return 0; }

- (id)   outlineView: (NSOutlineView *)ov
         child:(int)index
         ofItem:(id)item { return nil; }

- (id)   outlineView: (NSOutlineView *)ov
         objectValueForTableColumn:(NSTableColumn*)col
         byItem:(id)item { return nil; }


Two: When using NSTreeController, the "item" objects handed to you in the drag-and-drop datasource methods are actually instances of the private _NSArrayControllerTreeNode class. There's no real way to communicate with them directly. This may seem like a dead end, but through the magic of runtime binding, we can go ahead and use some private API to get at the "real" object:

- (BOOL) outlineView: (NSOutlineView *)ov
          acceptDrop: (id )info
                item: (id)item
          childIndex: (int)index
{
    item = [item observedObject];
    
    // do whatever you would normally do with the item
}


This will generate a compiler warning but you can make it go away by making up some random NSObject category that declares -observedObject. As an extra bonus, when you're passed an array of items, you can take advantage of NSArray's -valueForKey: implementation:

NSArray *myItems = [items valueForKey:@"observedObject"];

That's all! I haven't tried this with the browser view, but I expect it's basically the same there.

Don't let this turn you off to NSTreeController in general. The things it does it does really well. In particular, recursive child relationships "just work," which is really useful when you have an entity with a relationship that references itself.

Some folks run for the hills at the first sign of trouble in bindings. In most cases, you'll end up with far less code by implementing a few workarounds. Make things easy on yourself and only write code where you have to.
Design Element
NSTreeController and Drag and Drop
Posted Jun 18, 2005 — 17 comments below




 

KB — Jun 19, 05 250

This is really useful, thanks. I was wondering if you were planning to expand this into an NSTreeController/Core Data tutorial for CocoaDevCentral at any point? Your bindings and Core Data tutorials over there are brilliant and there is barely anything out there on NSTreeController at the moment.

Scott Stevenson — Jun 19, 05 252 Scotty the Leopard

You're not the first person to ask, so yes, I'll do that.

KB — Jun 19, 05 253

Great - thanks! I look forward to seeing it.

nobody — Jul 19, 05 308

That was a life-saver. How in the world did you find out about observedObject?

Matt Holiday — Aug 21, 05 344

I've written an extension to the OutlineEdit Core Data example (named DragAndDropOutlineEdit) which supports cut/copy/paste and drag-n-drop and gets a bit deeper into the opaque objects behind the tree controller. It also shows how to copy & paste portions of a managed object graph (via relationships), not just "flat" attributes of a managed object.

See http://homepage.mac.com/matthol2/cocoa/.

Jason Terhorst — Mar 06, 06 910

Is there an example project out on the net somewhere that has an NSTreeController, without the CoreData ties? Sort of like a DNDArrayController, except that it's a subclass of NSTreeController instead? Google isn't showing me anything right now.

Matthieu Cormier — Mar 06, 06 911

CoreData and NSTreeController? Fresh off the presses Jason.

http://allusions.sourceforge.net/articles/treeDragPart1.php

and

http://allusions.sourceforge.net/articles/treeDragPart2.php

Jason Terhorst — Mar 06, 06 913

What I meant was, how can I continue to use my old model class and a simple NSTreeController with drag and drop? I'm trying to avoid using CoreData or any entities (since my model uses some advanced attributes and data types) ... only my model class, with the usual bindings to an NSOutlineView. Is this doable? No CoreData, just a custom model class...

Tim — Mar 09, 06 924

Great Information, thanks!

Is it safe to use the valueForKey:@"observedObject" for getting information about the private _NSArrayControllerTreeNode? I mean, what are the changes that Apple will continue to support this in-official attribute for future development?

Tim

Scott Stevenson — Mar 13, 06 931 Scotty the Leopard

All observedObject does is return the actual object that the proxy refers to. Given that it's private API, it is possible that this would go away in the future, but I've been told by people that should know that this will probably keep working.

In reality, even if this API did go away, the necessary changes would probably be very minor. A small price to pay for something that works in the here and now.

Frederick C. Lee — Mar 19, 06 941

Great write-up.
I would love to see an example using CORE DATA + NSTreeController; specifically something with multiple nodes:

Region -->> Country -->> Province -->> City
| | | |
Maps Maps Maps Maps

Should I chain NSTreeControllers together?
How do I set up the respective Children Key Path/NSTreeController?
I understand the theory, but am getting confused with the implementation.

It appears I would need two (2) NSTreeControllers per region entity:
1) link to subRegion, 2) link to map.

That is, a Region's subRegion & map are siblings; one being a Leaf node (the map).

I would like to see a demo of something of this caliber.

Sam Stigler — May 07, 07 4064

Wow- first comment in over a year! Can anyone please confirm for me that the abovementioned ways of accessing NSManagedObject no longer work? (It's either that, or I've got a big problem with my NSManagedObject subclasses....)

Sam Stigler — May 08, 07 4079

Never mind; I just solved my problem. Here's the method I wrote for my subclass to do the same thing as [value valueForKey:@"observedObject"]

-(NSManagedObject *)observedObject { NSManagedObjectID *objID = [self objectID]; NSManagedObjectContext *moc = [self managedObjectContext]; id tmpObject = [moc objectWithID:objID]; return tmpObject; }

Rob — Oct 19, 07 4779

I'd like to see some thoughts on how best to implement search with NSTreeController. Think, how does one make a searchable outline using NSOutlineView and NSTreeController?

Paul Collins — Nov 23, 07 5134

On Leopard (as observed on 10.5.1), use of the private method -observedObject still works but logs a helpful "is deprecated" warning that points to the new public API: [NSTreeNode -representedObject].

I'm able to get my objects in -outlineView:writeItems:toPasteboard: using -representedObject the same as described for -observedObject in Scott's original post.

BTW, my results seem to differ with Sam's, in that -observedObject is still usable as described by Scott as of 10.4.11.

I'm not sure if the dummy NSOutlineViewDataSource methods are still needed in 10.5 (I'm leaving them in as I'm building for minimum 10.4 anyway).

Eric Man — Feb 01, 08 5433

I just found this: http://developer.apple.com/samplecode/AbstractTree/index.html
Since I don't have 10.5 yet I couldn't take a look, but I think it is relevant to your post.

Josh — May 30, 09 6784

One Question.
Where do I put this code?




 

Comments Temporarily Disabled

I had to temporarily disable comments due to spam. I'll re-enable them soon.




Technorati Profile
Copyright © Scott Stevenson 2004-2008