A Moveable Beast
February 9th, 2007Implementing reordering table views is something of a first-time obstacle in Cocoa. The basic gist is you take responsibility for dragging from and to the table view in question, and thereby implicitly support drag reordering. This technique is demonstrated by Apple’s DragNDropOutlineView example.
Faced with the desire to implement such functionality in a bindings-oriented application (FlexTime), I realized it would make sense to have the dragging handled by a the NSArrayController, which is already handling so much of the behavior associated with the table view.
RSRTVArrayController implements a subclass of NSArrayController whose responsibilities are extended to provide simple row reordering via Drag-and-Drop. The basic gist is:
- Drop this one class into your project.
- Drag the header file to Interface Builder
- Set it as the custom class on an array controller instance
- Connect the dataSource and delegate outlets from the table view to the controller.
- Connect the oTableView outlet from the controller to the table view.
Et voila!
You can also temporarily disable reordering by calling “setDraggingEnabled:” on the controller. I do this for instance in FlexTime while the routine is running so users can’t completely confuse my world.
Update: Rick Fillion pointed out a possible day-ruining bug in the code, which will only rear it’s head if you happen to be assigning all ownership of your model objects to the array controller. Since the “move” code accomplishes the move by removing and then inserting an object from the array, it turns out that once it’s removed, it’s deallocated if nothing else in your app is retaining it! So the workaround is to be retain the object while moving it, and then release it. Thanks for reporting that, Rick. The downloadable class is now updated.
February 9th, 2007 at 10:57 am
I wonder if this is the “right place” to handle the reordering, if the results don’t show up in the model, since the NSArrayController is supposedly the arbiter between the view and the model.
I suppose it’s as good as anywhere else, though, since it’s handling aspects of the view vs. the controller, if nothing else.
February 9th, 2007 at 11:00 am
Chris: I probably should have mentioned… the results DO show up in the model. The reordering happens in the array controller and is therefore propagated down into the model.
In my case it makes sense for the ordering to stick to the model, but I can see how it wouldn’t make sense for all such tables.
February 9th, 2007 at 11:01 am
Yep, you’re right–if the NSArrayController is mapping the array to the table 1-1, then the ordering would likely matter in most models.
February 9th, 2007 at 1:00 pm
Sounds very much like AXCArrayControllerWithDragAndDrop, from the Adium Xtras Creator.
February 9th, 2007 at 1:03 pm
Peter: indeed – though I see you delegate drag responsibility to a “validator,” which might be a good idea.
February 9th, 2007 at 2:47 pm
Sounds very much like BoundNSTableViewDragAndDropDataSource too ;-) I guess we all have to roll one of these sooner or later. (I also have a variant for positioned Core Data managed objects, but haven’t pushed it out yet).
February 9th, 2007 at 5:36 pm
Fantastic. Began rolling this myself last year without actually sitting back and thinking it through properly. Some of the stuff just felt natural in the NSArrayController subclass. However I’d put off doing it properly with some sit back and think design. Now I have three options to choose from to save me the time. Now if only CoreData and NSArrayController would support ordered entities – the concept is so common there are 3 open-source solutions !
February 10th, 2007 at 2:11 am
For CoreData also see:
Core Data, Bindings, and Sorting in an NSTableView: The Easy Way
http://words.fadeover.org/archives/13
February 15th, 2007 at 5:07 am
3 soultions – yes, now which one to choose! Persisting order in Core Data managedObjects… what’s the best method? Just writing out integers? Or setting relationships? I’ve experimented with the latter, but had all kinds of circular relationship errors and concequent saving errors. I guess I was not being careful enough. So the former works best for me. Is there a better way? (I’m still new)
February 22nd, 2007 at 6:39 am
Just coming back to this to try and work out which of the three to use. The Adium version doesn’t actually handle ordering itself but simply delegates to another arbitrary object where you have to do the work.
The biggest headache I came across when trying to achieve this was discontiguous selections in the drag operation. I’ve got a feeling that Daniel’s won’t handle it cos it looks awfully similar to the code that I came up with. Daniel, does it work with discontiguous selections?
The BoundNSTableViewDragAndDropDataSource has simpler re-ordering code and is novel because it uses a reverse ordering on the enumeration, which makes me wonder whether it would handle this case? I know I can test it, but anyone know from experience?
February 22nd, 2007 at 6:47 am
Hmm – you know I hadn’t really tested the discontinuous scenario. I just tried it live on FlexTime (download and try it!), which uses the code.
The short answer is: it works as well as I’d guess it would. The discontiguous items all get moved such that they line up, in order, at the point where they are dragged to. They don’t make any effort to interspace themselves in the list at the same frequency as they were selected, if that’s what you’re looking for.
February 22nd, 2007 at 1:59 pm
I had problems because my app already had a delegate and datasource object, for accepting drops from other table views, and something went bitter and twisted when I tried to merge the two. Ah well.
February 24th, 2007 at 8:45 am
Daniel,
sorry, should have just tested it myself! Yes it seems to work fine the way I would want in FlexTime. I think remembering distribution would be very intuitive. Trying to remember exactly what was wrong with my implementation from six months ago (I’ve removed the code from my head revision) I think I had it working fine but it was going screwy on Undo, I believe because I had (bug ridden) overriddes the add / insert methods of the NSArrayController to also add auto-generation of sequential ID’s and the problems had arisen there as a result of not sitting down and designing it first.
So I think I will make very good use of RSRTVArrayController as it doesn’t exhibit any of the problems I created for myself. Thanks.
Sanjay
February 24th, 2007 at 8:46 am
Remembering distribution would NOT be very intuitive.
March 2nd, 2007 at 1:56 am
is there any version of Cocoa works under windows.!
March 2nd, 2007 at 7:33 am
KFUPM: Not really – Apple owns Cocoa and makes it available only on Mac computers. But there is an open source project called GNUStep which tries to replicate some of the functionality.
March 2nd, 2007 at 7:51 am
Try CocoaTron as well
http://groups.google.com/group/cocotron-dev?hl=en
March 7th, 2007 at 10:35 pm
I have been thinking of core data and order as well lately. My stab at it with a double linked list (minus the NSArrayController… that’s next, I fear) is available on my blog.
http://www.jonathansaggau.com/blog/2007/02/core_data_double_linked_list_h.html
svn co http://jonathansaggau.com/svn/MOLinkedList