A Moveable Beast

February 9th, 2007

Implementing 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:

  1. Drop this one class into your project.
  2. Drag the header file to Interface Builder
  3. Set it as the custom class on an array controller instance
  4. Connect the dataSource and delegate outlets from the table view to the controller.
  5. 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.

18 Responses to “A Moveable Beast”

  1. Chris Ryland Says:

    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.

  2. Daniel Jalkut Says:

    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.

  3. Chris Ryland Says:

    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.

  4. Peter Hosey Says:

    Sounds very much like AXCArrayControllerWithDragAndDrop, from the Adium Xtras Creator.

  5. Daniel Jalkut Says:

    Peter: indeed – though I see you delegate drag responsibility to a “validator,” which might be a good idea.

  6. rentzsch Says:

    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).

  7. ssanchex Says:

    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 !

  8. chad Says:

    For CoreData also see:
    Core Data, Bindings, and Sorting in an NSTableView: The Easy Way
    http://words.fadeover.org/archives/13

  9. mathieu Says:

    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)

  10. ssanchex Says:

    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?

  11. Daniel Jalkut Says:

    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.

  12. mathieu Says:

    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.

  13. ssanchex Says:

    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

  14. ssanchex Says:

    Remembering distribution would NOT be very intuitive.

  15. KFUPM Says:

    is there any version of Cocoa works under windows.!

  16. Daniel Jalkut Says:

    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.

  17. mathieu Says:

    Try CocoaTron as well

    http://groups.google.com/group/cocotron-dev?hl=en

  18. Jonathan Saggau Says:

    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

Comments are closed.

Follow the Conversation

Stay up-to-date by subscribing to the Comments RSS Feed for this entry.