Setting Up NSOutlineView Drag and Drop with Core Data in Cocoa
Last week, I finally got around to building the number-one VirtualHostX feature request – groups/folders in the sidebar. I had put off implementing this feature for years because I never knew quite where to start when it came to Core Data and NSOutlineView. But, with VHX 5.0 coming out later this year, I decided to lock myself in my office and not come out until I had a sorted, drag-and-droppable, outline view working. In the end, it only took about three hours to get all the pieces in place. And while I'm not quite ready to release this feature to my users, I thought I'd jot down some tips and code snippets that I found helpful while it's still fresh in my mind. Google wasn't particularly helpful with any recent results on the topic, so maybe this'll be found by someone struggling with the same problem.
Setting up your XIB
In your Core Data model, create a one-to-many relationship called children pointing back to your same entity. Then create a corresponding one-to-one relationship called parent back to the same entity. The trick here is that you're not going to have an entity for groups/folders and then another entity for your actual items (in my case, virtual hosts). They're all going to be the same entity, but with a property called isFolder that allows you to differentiate between the two types.
In your NIB file you'll need an array controller bound to your NSManagedObjectContext, set to Entity mode, and with a Fetch Predicate of parent == nil. This will fetch our top level objects – entities without a parent.
Next, create a tree controller with its Children Key Path set to children, set to Entity mode, and its Content Array bound to your array controller's arrangedObjects.
Finally, drag an NSOutlineView to your NIB. Set its dataSource and delegate to one of those blue NSObject's, which you'll subclass to be an OutlineViewController you'll create in code later on.
Connect the IBOutlets back to the appropriate objects in your NIB.
Here's the first big chunk of OutlineViewController.h/m
Managing the outline item's expanded state
We're so spoiled with how much crap work Cocoa handles for us, I naively thought that NSOutlineView would just automatically remember the collapsed/expanded state of the items in my tree. It turns out, there is a way to have NSOutlineView persist those states to NSUserDefaults, but I could never get that to work. So I just do it all manually – it's not that hard. Just add a BOOL to your Core Data model called isExpanded and then further down in
And then subclass your NSOutlineView's reloadData method to be:
And that's basically it. Overall, not as much code as I would have expected (thanks to bindings, natually). Hope the above code helps anyone tackling the same problem. Much of my code was discovered/cribbed from this great blog post on the topic from a few years ago.