Table Drag & Drop Oddity

I have a mostly successful implementation of dragging and dropping within a table. However, the two lines below will only execute the first and not the second (depending on their order). No error is generated and the script does continue beyond this point…

Code is located inside of

refUITableArrayController's insertObject_atArrangedObjectIndex_(listData, numTargetRow)
refUITableArrayController's remove_(numRow)
--> results in a new row being added but the original row is not deleted
refUITableArrayController's remove_(numRow)
refUITableArrayController's insertObject_atArrangedObjectIndex_(listData, numTargetRow-1)
--> results in a original row being deleted but the new row not being added

I’m at a loss to explain why each line will work when placed first in order but not second. Note that in the second example, I am subtracting 1 to account for the deleted original row.

In both cases, refUITableArrayController is my referencing outlet to my table’s array controller.

Also, these same two lines together work great when I’m replacing a line within a table (deleting a line and putting a new one in it’s exact location. So, it is frustrating that this doesn’t work.

The entire routine:

on tableView_acceptDrop_row_dropOperation_(tv, info, numTargetRow, proposedOp)
	--	This subroutine handles a drop command
	try
			
		--	Get data from the pasteboard
		set dataPasteboard to info's draggingPasteboard()
		set numRowDragged to dataPasteboard's stringForType_("wtmColumnDataType")

		--	Insert the moving item into the list at the drop position
		set numRowDragged to ((numRowDragged as text) as integer) 

		set numTargetRow to (numTargetRow as integer)

		--	Get the current selection's data and then delete it
		tell refUITableArrayController to set recDataSelected to selectedObjects() as list
		set listData to item 1 of recDataSelected
		refUITableArrayController's remove_(numRow)
			
		--	Add the deleted row at the new location
		tell refUITable to set numRow to selectedRow()	
		refUITableArrayController's insertObject_atArrangedObjectIndex_(listData, numTargetRow)			
end

Any thoughts?

Often it’s a question of the lifetime of objects.

If you are going to drag an object to change its order in the table view, the object is technically removed and then reinserted at the now location.

Objects are dynamically allocated and released in memory controlled by reference counting.
Creating an object sets the counter to 1, adding the object to collection objects increments the counter,
removing the object decrements the counter. When the counter reaches 0 the object and its reference will be destroyed.

Imagine the reference counter is 1:
When you first remove the object from the array the reference counter will be decremented to 0 and the object will be destroyed. It cannot be inserted

When you first insert the object at the new location in the array, the reference counter will be incremented to 2.
After removing the object it will be reset to 1 and the object is still living.

As the remove/insert operations are too fast to be noticed, you’ll never see the duplicate

But I am seeing a duplicate… at least in the one example, because the item isn’t being deleted. In the other, it isn’t being inserted.

I don’t understand your response or am missing something. To summarize…I am:

  1. getting the data of the item that was selected and putting it into a variable
  2. using that variable to insert a duplicate item at the new location
  3. trying to go back and delete the original item that still exists in the table (not working)

OR

  1. getting the data of the item that was selected and putting it into a variable
  2. deleting the original item in the table
  3. using the variable of the data to insert a duplicate item at the new location (not working)

I just tried to explain

What process is doing that? My drag and drop is only notifying my routine of the number dragged and the position dropped. Without my code attempting to delete and insert, the item snaps back to it’s original position. I’m not sure what you are describing…

What counter are you talking about? Is this a property of the table or the array… and how would I use that to solve my problem?

To provide drag & drop for a table view controlled by an array controller
you have to subclass NSArrayController and override/add the appropriate methods

I’m talking about the basics about memory management

But in the first, you’re not modifying numRow to allow for the insertion.

That’s just an observation right… not the source of my problem? I have since realized that depending on if the user drags up or down (above or below the selection) I may need to adjust those numbers. But I don’t think that error you point out solves the issue… correct? Or am I missing something?

Right.

I usually check to see which number is biggest, and swap order of insertion/deletion to suit. If the new position is the bigger number, I insert first; otherwise I delete first. That way the numbers don’t need to change.

FIXED IT!!!

This code works great for anyone interested. Note that I am selecting the dragged row, then getting its data using selectedObjects(). That’s in case the user drags a row they are not clicked on. I’d be curious to know if there is a command to get the data from an array controller for a row that is not selected as I didn’t see one in the documentation (though I may have overlooked it). Any other suggestions on improving this code would be welcome as well.

	on tableView_acceptDrop_row_dropOperation_(tv, info, numTargetRow, proposedOp)
		--	This subroutine handles a drop command
		try
			
			--	Get data from the pasteboard
			set dataPasteboard to info's draggingPasteboard()
			set numRowDragged to dataPasteboard's stringForType_("wtmColumnDataType")
			set numRowDragged to ((numRowDragged as text) as integer) 
			set numTargetRow to (numTargetRow as integer)
			
			--	Adjust the target row if we are going to be deleting above the dragged row
			if numRowDragged < numTargetRow then
				set numTargetRow to numTargetRow - 1
			end
			
			--	Get the data from the dragged row
			tell class "NSIndexSet" of current application
				set recIndexSet to indexSetWithIndex_(numRowDragged)
			end tell
			tell refUITable to selectRowIndexes_byExtendingSelection_(recIndexSet, false)
			tell refUITableArrayController to set recDataSelected to selectedObjects() as list
			set listData to item 1 of recDataSelected
			
			--	Delete the original row's location & insert it's duplicate in the dropped location
			tell refUITableArrayController
				remove_(numRowDragged)
				insertObject_atArrangedObjectIndex_(listData, numTargetRow)
			end
			
			--	Selected the row at it's new dropped location
			tell class "NSIndexSet" of current application
				set recIndexSet to indexSetWithIndex_(numTargetRow)
			end tell
			tell refUITable to selectRowIndexes_byExtendingSelection_(recIndexSet, false)
			tell refUITableArrayController to set recDataSelected to selectedObjects() as list
			set listData to item 1 of recDataSelected
						
			return true
			on error
			return false
		end try
	end tableView_acceptDrop_row_dropOperation_

You should also be implementing tableView_writeRowsWithIndexes_toPasteboard_. You don’t actually have to write the stuff to the pasteboard – you can store it in a property – but it gives you an index set of the of the items being dragged, which you can then use in tableView_acceptDrop_row_dropOperation_.

I am using tableView_writeRowsWithIndexes_toPasteboard_ but that only gives me the index of the row dragged.

on tableView_writeRowsWithIndexes_toPasteboard_(tv, indexSet, dataPasteboard)

	set numRowDragged to (indexSet's firstIndex()) as integer as text
	dataPasteboard's declareTypes_owner_({"wtmColumnDataType"}, me)
	dataPasteboard's setString_forType_(numRowDragged, "wtmColumnDataType")
end tableView_writeRowsWithIndexes_toPasteboard_

Is that appropriate.

What I’m curious about is if there is a command that will give me the contents of any row, not just the selected one… independent of a drag operation… just in general. I would use it when the drop occurs rather than selecting the dragged row and then getting it’s values. Does an array controller have a method that allows me to get the contents of any row I please?

You can ask the array controller for arrangedObjects()'s objectAtIndex_(n).

That’s what I was looking for… thanks!!