Skip to content

Drag and Drop Rearranging in Table Views with Core Data

2008 July 17

One thing I frequently need to support in my applications is the display of data in a table view with a specific order (not necessarily alphabetical/numerical order) along with the ability to rearrange that order by dragging and dropping items in the table.

In this article I’ll look at how I accomplish this task using these steps:

  1. Setup order in Core Data by maintaining a ‘viewPosition’ for each object.
  2. Write code to support rearrangement of the order (ie moving an object(s) above/below another object in the user interface).
  3. Setup Drag and Drop on the NSTableView to call the rearrangement code.

A sample project for the final application is available to download from here: coredatadragdrop.zip.

Maintaining Order with Core Data

Core Data notoriously pays no attention to the order of objects. If you insert three objects into the data store and then query that store, those objects might be returned in any order (specifically, they probably won’t be returned in the order in which you put them in). The way to get Core Data to arrange objects in a specific order is to use Sort Descriptors.

You might decide that, by default, you want Core Data to display objects in the order in which they were created; one way to accomplish this would be to add an attribute in your Entity Description that kept track of the object creation date/time and then sort your array controller or fetch predicate by this attribute. This would be fine for some applications but for the purposes of allowing the user to rearrange data into a different order, it would not make sense; each time the user moved one ‘younger’ object above an ‘older’ object, you’d have to change the creation date/time to an arbitrarily ‘greater’ value.

So, we need to maintain a ‘viewPosition’ attribute in our managed objects. For the three objects we stored earlier, the first would have a viewPosition of 0, the second a position of 1 and the third, 2. That way you can sort your fetched results on the viewPosition and the objects will always appear in the correct order.

Clearly there is huge scope here for the viewPosition to become corrupted — for example if you delete an object, the remaining objects will not have consecutive viewPositions. Depending on how you code dependent methods, this could be a problem. It’s necessary, therefore, to have a rearrange method that runs through the objects and resets all the viewPositions whenever such a change occurs.

Initial Values

When creating Tables in a Microsoft SQL Server database, for example, you can use an Identity seed to assign automatically incremental values to new objects. Core Data provides no such easy mechanism so you have to write your own.

One solution would be to use a custom subclass for your object that somehow kept track of how many instances there were so that it could assign ‘the next’ value on object creation. In this article, however, we’ll set the initial value of an object to a pre-defined ‘Temporary’ value that is subsequently updated by a ‘rearrange’ method. And, to avoid confusion between our viewPosition rearrangement and the methods that rearrange on for example an NSArrayController, I tend to use a method called ‘renumber’ rather than ‘rearrange’.

Temporary Values

If the viewPosition of an object is 0 for the first object, 1 for the second etc we can use negative numbers for temporary values that define how the objects are renumbered.

We need a plain temporary value for objects that should be ignored by a renumber method — let’s use -1. We also need a value for objects that should be placed at the top of the list — use -2. Finally, we need a value for objects that should end up at the bottom of the list — you guessed it, we’ll use -3.

If we give a newly created object value a viewPosition of -2 and then call a rearrange method, the method can renumber any -2 objects by starting at 0, and then tag on the remaining objects so that our new object appears at the head of the line. Similarly, for -3 objects it would cycle through all the other objects and then set the viewPosition of the -3 object to be one increment higher than the last object so it appears at the end of the line.

By using temporary values we get several benefits; firstly, we never need to maintain an up-to-date object count. Secondly, we can use the values to determine the order in which newly created objects appear in a list.

Beginning our Project

Before we get into any coding etc, we need to create a demo application.

  • Create a new Xcode Core Data Document-based application (I like to use Document-based apps for demos as you can save/open different sample data as you wish rather than having everything stored automatically by the application).
  • Setup your data model with one Entity called ‘Item’ that has a ‘name’ string attribute and a ‘viewPosition’ integer attribute.
  • Design your interface with a two-column NSTableView bound to an ‘Items’ NSArrayController that pulls the two attributes for the Item entity. Add + and – buttons and connect them to the NSArrayController. My interface looks like this:
    The Interface Builder design of the main document window
  • Make sure the Items array controller is set to Prepare Content and Auto Rearrange Content (Making the array controller Auto Rearrange Content will force the display to update whenever we change an object’s viewPosition). Don’t forget to bind its managed object context to File’s Owner. Also set the NSTableView to allow Multiple Selection.
  • Build and run the application to check that adding and removing items works as expected.

Now that our basic demo app is working, let’s change the ‘add’ and ‘remove’ behaviour so that the work is done in code rather than by IB actions on the NSArrayController.

  • In the MyDocument interface add an IBOutlet called itemsArrayController for the NSArrayController, along with two IBActions addNewItem: and removeSelectedItems:.
  • Connect the itemsArrayController to the Items NSArrayController in Interface Builder. Also change the behaviour of the + and – buttons to call our new File’s Owner actions.
  • Add code for these actions as follows:
    - (IBAction)addNewItem:(id)sender
    {
    	NSManagedObject *newItem = [NSEntityDescription insertNewObjectForEntityForName:@"Item" inManagedObjectContext:[self managedObjectContext]];
     
    	[newItem setValue:@"New Item" forKey:@"name"];
    	[newItem setValue:[NSNumber numberWithInt:-1] forKey:@"viewPosition"];
    }
     
    - (IBAction)removeSelectedItems:(id)sender
    {
    	NSArray *selectedItems = [itemsArrayController selectedObjects];
     
    	int count;
    	for( count = 0; count < [selectedItems count]; count ++ )
    	{
    		NSManagedObject *currentObject = [selectedItems objectAtIndex:count];
    		[[self managedObjectContext] deleteObject:currentObject];
    	}
    }

If you wish, build and run the application again to make sure that the buttons still work. This time when you click the + button, the new item will be called “New Item” and have a temporary viewPosition (-1).

As we’re going to be making quite a bit of use of our temporary viewPosition values, let’s set up friendly names for them. We could use either #define or global variables for this; here I’ll use a mixture of both:

  • Add global variables at the top of your MyDocument.m file for the three different types of temporary values:
    int temporaryViewPosition = -1;
    int startViewPosition = -2;
    int endViewPosition = -3;
  • As we’ll frequently be needing these as an NSNumber value, add relevant #define lines:
    #define temporaryViewPositionNum [NSNumber numberWithInt:temporaryViewPosition]
    #define startViewPositionNum [NSNumber numberWithInt:startViewPosition]
    #define endViewPositionNum [NSNumber numberWithInt:endViewPosition]
  • Change the addNewItem: method to use the named value rather than explicitly using -1:
    	[newItem setValue:temporaryViewPositionNum forKey:@"viewPosition"];

So that the items are displayed in the correct order, we’ll maintain a _sortDescriptors array in MyDocument that is accessed via a sortDescriptors: method:

  • Add to the MyDocument interface a _sortDescriptor NSArray pointer and a method to access it.
    	NSArray *_sortDescriptors;
    - (NSArray *)sortDescriptors;
  • Implement the sortDescriptors: method like this:
    - (NSArray *)sortDescriptors
    {
    	if( _sortDescriptors == nil )
    	{
    		_sortDescriptors = [NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"viewPosition" ascending:YES]];
    	}
    	return _sortDescriptors;
    }
  • In Interface Builder, bind the Sort Descriptors of the Items NSArrayController to File’s Owner ‘sortDescriptors’ key. If you want to be able to test the sort descriptors by changing the viewPosition values, you’ll need to add a Number Formatter to the View Position column in the table view.
  • Lastly, hide the column headings on the table view so that the user can’t sort the data by clicking on a heading.

Renumbering Items

We need to write a method to go through all the objects and renumber them correctly. This method needs to do the following things:

  1. Identify existing objects that have a ‘permanent’ viewPosition of 0 or higher (ie that should stay in their existing order)
  2. Identify objects with a viewPosition equal to startViewPosition
  3. Identify objects with a viewPosition equal to endViewPosition
  4. Renumber the items with the order:
    • Items with a viewPosition of startViewPosition
    • Items with a viewPosition of 0 or higher
    • Items with a viewPosition of endViewPosition

Helper Methods

To make our code easier to handle and more concise we’ll write a few helper methods that will:

  1. Return an array of objects with the result of a fetch with specified predicate
  2. Return an array of objects with a specified viewPosition
  3. Return an array of objects with a ‘non temporary’ viewPosition (greater than or equal to zero)
  4. Return an array of objects with a viewPosition greater than or equal to a specified value
  5. Return an array of objects with a viewPosition between two specified values
  6. Renumber the viewPositions in an array of objects, starting with a specified value

So, add the following code for these helper method implementations to your MyDocument object (don’t forget to modify the interface in the MyDocument.h header file too):

- (NSArray *)itemsUsingFetchPredicate:(NSPredicate *)fetchPredicate
{
	NSError *error = nil;
	NSEntityDescription *entityDesc = [NSEntityDescription entityForName:@"Item" inManagedObjectContext:[self managedObjectContext]];
 
	NSArray *arrayOfItems;
	NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
	[fetchRequest setEntity:entityDesc];
	[fetchRequest setPredicate:fetchPredicate];
	[fetchRequest setSortDescriptors:[self sortDescriptors]];
	arrayOfItems = [[self managedObjectContext] executeFetchRequest:fetchRequest error:&error];
	[fetchRequest release];
 
	return arrayOfItems;
}
 
- (NSArray *)itemsWithViewPosition:(int)value
{
	NSPredicate *fetchPredicate = [NSPredicate predicateWithFormat:@"viewPosition == %i", value];
 
	return [self itemsUsingFetchPredicate:fetchPredicate];
}
 
- (NSArray *)itemsWithNonTemporaryViewPosition
{
	NSPredicate *fetchPredicate = [NSPredicate predicateWithFormat:@"viewPosition >= 0"];
 
	return [self itemsUsingFetchPredicate:fetchPredicate];
}
 
- (NSArray *)itemsWithViewPositionGreaterThanOrEqualTo:(int)value
{
	NSPredicate *fetchPredicate = [NSPredicate predicateWithFormat:@"viewPosition >= %i", value];
 
	return [self itemsUsingFetchPredicate:fetchPredicate];
}
 
- (NSArray *)itemsWithViewPositionBetween:(int)lowValue and:(int)highValue
{
	NSPredicate *fetchPredicate = [NSPredicate predicateWithFormat:@"viewPosition >= %i && viewPosition <= %i", lowValue, highValue];
 
	return [self itemsUsingFetchPredicate:fetchPredicate];
}
 
- (int)renumberViewPositionsOfItems:(NSArray *)array startingAt:(int)value
{
	int currentViewPosition = value;
 
	int count = 0;
 
	if( array && ([array count] > 0) )
	{
		for( count = 0; count < [array count]; count++ )
		{
			NSManagedObject *currentObject = [array objectAtIndex:count];
			[currentObject setValue:[NSNumber numberWithInt:currentViewPosition] forKey:@"viewPosition"];
			currentViewPosition++;
		}
	}
 
	return currentViewPosition;
}

Note that in a real-world application it would make sense to bundle these methods into a separate helper object (or at least a separate file) to avoid unnecessary bloating of the MyDocument object; for ease of following code in this article though we’ll keep them all in the same file.

We can now use these helper functions to code our renumbering method:

- (void)renumberViewPositions
{
	NSArray *startItems = [self itemsWithViewPosition:startViewPosition];
 
	NSArray *existingItems = [self itemsWithNonTemporaryViewPosition];
 
	NSArray *endItems = [self itemsWithViewPosition:endViewPosition];
 
	int currentViewPosition = 0;
 
	if( startItems && ([startItems count] > 0) )
		currentViewPosition = [self renumberViewPositionsOfItems:startItems startingAt:currentViewPosition];
 
	if( existingItems && ([existingItems count] > 0) )
		currentViewPosition = [self renumberViewPositionsOfItems:existingItems startingAt:currentViewPosition];
 
	if( endItems && ([endItems count] > 0) )
		currentViewPosition = [self renumberViewPositionsOfItems:endItems startingAt:currentViewPosition];
}

If you wish to test this method, add a ‘Renumber’ button in Interface Builder that calls an IBAction that in turn calls this renumberViewPositions: method and try it with some test data (set the view position of test items to -2, -3 or greater than zero etc and check the renumbering). Again, you’ll need to have added a Number Formatter to the View Position column.

Running application showing before and after renumbering

Changing the Add Behaviour

Now that we have our renumbering methods in place, we can go ahead and change our addNewItem: and removeSelectedItems: methods so that they call the renumberViewPositions: method to update the viewPositions:

- (IBAction)addNewItem:(id)sender
{
	NSManagedObject *newItem = [NSEntityDescription insertNewObjectForEntityForName:@"Item" inManagedObjectContext:[self managedObjectContext]];
 
	[newItemView setValue:@"New Item" forKey:@"name"];
	[newItemView setValue:endViewPositionNum forKey:@"viewPosition"];
 
	[self renumberViewPositions];
}
 
- (IBAction)removeSelectedItems:(id)sender
{
	NSArray *selectedItems = [itemsArrayController selectedObjects];
 
	int count;
	for( count = 0; count < [selectedItems count]; count ++ )
	{
		NSManagedObject *currentObject = [selectedItems objectAtIndex:count];
		[[self managedObjectContext] deleteObject:currentObject];
	}
 
	[self renumberViewPositions];
}

This code will make new items appear at the bottom of the table. Naturally, if you wish them to appear at the top, you can instead set the viewPosition to be startViewPositionNum. In a real-world app you might want to let the user decide in your application preferences where new objects appear and vary this method accordingly.

Handling Drag and Drop in a Table View

Finally (and I’m sorry this has taken a while…) we arrive at a place where we can now start dealing with Drag and Drop.

Having a table view support Drag and Drop is actually quite easy — it requires you to do four things:

  1. Register the table for dragged types
  2. Handle a tableView: writeRowsWithIndexes: toPasteboard: method that fires when the user initiates a Drag
  3. Handle a tableView: validateDrop: proposedRow: proposedDropOperation: method to validate whether or not the table should accept what the user might be about to Drop
  4. Handle a tableView: acceptDrop: row: dropOperation: method that carries out the necessary work when a user has Dropped data in the table

We’ll go through these in order:

  • Add an IBOutlet for the NSTableView called itemsTableView and connect it in Interface Builder.
  • Add an NSString global variable to MyDocument.m that defines a unique identifier for our dragged items:
    NSString *DemoItemsDropType = @"DemoItemsDropType";
  • Add the following at the end of your windowControllerDidLoadNib: method:
    	[itemsTableView setDataSource:self];
    	[itemsTableView registerForDraggedTypes:[NSArray arrayWithObjects:DemoItemsDropType, nil]];

Now we need to write the delegate methods that handle table view drags and drops. In order that these are actually called, we need to make the MyDocument object the delegate of the table view.

  • In Interface Builder connect File’s Owner to be the delegate of the table view.
  • Implement the following method in MyDocument.m:
    - (BOOL)tableView:(NSTableView *)tv writeRowsWithIndexes:(NSIndexSet *)rowIndexes toPasteboard:(NSPasteboard*)pasteboard
    {
    	NSData *data = [NSKeyedArchiver archivedDataWithRootObject:rowIndexes];
    	[pasteboard declareTypes:[NSArray arrayWithObject:DemoItemsDropType] owner:self];
    	[pasteboard setData:data forType:DemoItemsDropType];
    	return YES;
    }

This method simply archives the dragged rowIndexes to the pasteboard ready for picking up when the user drops the items. If you build and run the application at this point, you will be able to click and drag an item in the list; no feedback is given by the table view about receiving the data and nothing will happen when you release that item.

To have the table view give feedback that it can receive the dragged items (ie a horizontal line where a dropped item will appear), we need to implement the validation delegate method:

  • Implement the following method in MyDocument.m:
    - (NSDragOperation)tableView:(NSTableView*)tv validateDrop:(id <nsdraggingInfo>)info proposedRow:(int)row proposedDropOperation:(NSTableViewDropOperation)operation
    {
    	if( [info draggingSource] == itemsTableView )
    	{
    		if( operation == NSTableViewDropOn )
    			[tv setDropRow:row dropOperation:NSTableViewDropAbove];
     
    		return NSDragOperationMove;
    	}
    	else
    	{
    		return NSDragOperationNone;
    	}
    }

This method first of all checks that the items are being dragged from this document’s table view rather than a table view in another document. Assuming they are, it checks to see whether the user is trying to drop right onto a row rather than above or below. If so, it changes the operation to drop above that row rather than directly on top of it.

If you build and run again you’ll find that when you drag items around, the table view gives feedback as to where the dropped items will end up. Actually dropping the items still does nothing.

The delegate method that handles the drop needs to do various things. First, it needs to extract the indexes of the rows that are being dropped and collect the items to which those indexes refer. It should set the viewPosition of these items to the temporaryViewPosition value so that they are not returned by subsequent fetches. Second, it needs to collect two arrays of items — one of objects with viewPosition less than and the other greater than the proposed drop row. Finally, it should renumber the viewPositions of the objects in these arrays in the correct order.

  • Implement the following method in MyDocument.m:
    - (BOOL)tableView:(NSTableView *)aTableView acceptDrop:(id <nsdraggingInfo>)info row:(int)row dropOperation:(NSTableViewDropOperation)operation
    {
    	NSPasteboard *pasteboard = [info draggingPasteboard];
    	NSData *rowData = [pasteboard dataForType:DemoItemsDropType];
    	NSIndexSet *rowIndexes = [NSKeyedUnarchiver unarchiveObjectWithData:rowData];
     
    	NSArray *allItemsArray = [itemsArrayController arrangedObjects];
    	NSMutableArray *draggedItemsArray = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
     
    	unsigned int currentItemIndex;
    	NSRange range = NSMakeRange( 0, [rowIndexes lastIndex] + 1 );
    	while([rowIndexes getIndexes:&currentItemIndex maxCount:1 inIndexRange:&range] > 0)
    	{
    		NSManagedObject *thisItem = [allItemsArray objectAtIndex:currentItemIndex];
     
    		[draggedItemsArray addObject:thisItem];
    	}
     
    	int count;
    	for( count = 0; count < [draggedItemsArray count]; count++ )
    	{
    		NSManagedObject *currentItemToMove = [draggedItemsArray objectAtIndex:count];
    		[currentItemToMove setValue:temporaryViewPositionNum forKey:@"viewPosition"];
    	}
     
    	int tempRow;
    	if( row == 0 )
    		tempRow = -1;
    	else
    		tempRow = row;
     
    	NSArray *startItemsArray = [self itemsWithViewPositionBetween:0 and:tempRow];
    	NSArray *endItemsArray = [self itemsWithViewPositionGreaterThanOrEqualTo:row];
     
    	int currentViewPosition;
     
    	currentViewPosition = [self renumberViewPositionsOfItems:startItemsArray startingAt:0];
     
    	currentViewPosition = [self renumberViewPositionsOfItems:draggedItemsArray startingAt:currentViewPosition];
     
    	currentViewPosition = [self renumberViewPositionsOfItems:endItemsArray startingAt:currentViewPosition];
     
    	return YES;
    }

The tempRow calculation is used if the user performs a drop at the top of the table. In this case, the incoming row value would be 0 so the object at viewPosition 0 would be modified twice (it would be picked up both by “itemsWithViewPositionBetween 0 and 0″ and “itemsWithViewPositionGreaterThan -1″) leaving the viewPositions out by a value of 1.

Hopefully you should by now have functioning drag and drop behaviour in the application. If you wish, remove the column that displays the View Position – this was simply to show the values as they change and make sure everything worked as it should.

It is important to note that the helper methods, renumbering methods and delegate methods do absolutely no error checking. In a real-world application, this would be a Certified Bad Thing.

Handling Copying

Normal Drag and Drop behaviour on the Macintosh allows you to copy what you’re dragging if you hold down the Option key. To allow this behaviour in your application, you need to do three things:

  1. Tell the table view what drag operations it supports
  2. Check for the option key during the validateDrop: delegate method and return the relevant drag operation
  3. Check the drag operation during the acceptDrop: method and behave accordingly

Way, way back, many centuries ago (1996) you needed to subclass NSTableView and override a draggingSourceOperationMaskForLocal: method in order to provide a dragging source operation mask. Tiger introduced a setDraggingSourceOperationMask: method that can be called on a standard table view to tell it what drag operations it can support. Since this article is primarily about Drag and Drop with Core Data and Core Data requires Tiger, we can quite happily use this useful method to avoid the hassle of subclassing NSTableView.

  • Add this line at the end of the windowControllerDidLoadNib: method:
    	[itemsTableView setDraggingSourceOperationMask:(NSDragOperationMove | NSDragOperationCopy) forLocal:YES];

This simply tells the table view that it can accept either Move operations or Copy operations from the Local application (ie from our demo application as apposed to other applications like the Finder).

Checking for the option key is fairly simple:

  • Change your validation method to the following:
    - (NSDragOperation)tableView:(NSTableView*)tv validateDrop:(id <nsdraggingInfo>)info proposedRow:(int)row proposedDropOperation:(NSTableViewDropOperation)op
    {
    	if( [info draggingSource] == itemsTableView )
    	{
    		if( op == NSTableViewDropOn )
    			[tv setDropRow:row dropOperation:NSTableViewDropAbove];
     
    		if(( [[[NSApplication sharedApplication] currentEvent] modifierFlags] & NSAlternateKeyMask ) )
    			return NSDragOperationCopy;
    		else
    			return NSDragOperationMove;
    	}
    	else
    	{
    		return NSDragOperationNone;
    	}
    }

This method now checks to see whether the option key is currently being pressed and if it is, it returns NSDragOperationCopy. By returning this value, your application will automatically display the little ‘+’ sign next to the cursor to indicate that the dragged item will be copied.

To make implementation of a copying mechanism easier, we’ll write a method that iterates through an array of existing objects, creating new objects that have copied name attributes and a temporaryViewPosition:

- (NSArray *)copyItems:(NSArray *)itemsToCopyArray
{
	NSMutableArray *arrayOfCopiedItems = [NSMutableArray arrayWithCapacity:[itemsToCopyArray count]];
 
	int count;
	for( count = 0; count < [itemsToCopyArray count]; count++ )
	{
		NSManagedObject *itemToCopy = [itemsToCopyArray objectAtIndex:count];
		NSManagedObject *copiedItem = [NSEntityDescription insertNewObjectForEntityForName:@"Item" inManagedObjectContext:[self managedObjectContext]];
 
		[copiedItem setValue:[itemToCopy valueForKey:@"name"] forKey:@"name"];
		[copiedItem setValue:temporaryViewPositionNum forKey:@"viewPosition"];
 
		[arrayOfCopiedItems addObject:copiedItem];
	}
 
	return arrayOfCopiedItems;
}

Finally let’s change the acceptDrop: method so that it handles NSDragOperationCopy as well as NSDragOperationMove:

- (BOOL)tableView:(NSTableView *)aTableView acceptDrop:(id <nsdraggingInfo>)info row:(int)row dropOperation:(NSTableViewDropOperation)operation
{
	NSPasteboard *pasteboard = [info draggingPasteboard];
	NSData *rowData = [pasteboard dataForType:DemoItemsDropType];
	NSIndexSet *rowIndexes = [NSKeyedUnarchiver unarchiveObjectWithData:rowData];
 
	NSArray *allItemsArray = [itemsArrayController arrangedObjects];
	NSMutableArray *draggedItemsArray = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
 
	unsigned int currentItemIndex;
	NSRange range = NSMakeRange( 0, [rowIndexes lastIndex] + 1 );
	while([rowIndexes getIndexes:&currentItemIndex maxCount:1 inIndexRange:&range] > 0)
	{
		NSManagedObject *thisItem = [allItemsArray objectAtIndex:currentItemIndex];
 
		[draggedItemsArray addObject:thisItem];
	}
 
	if( [info draggingSourceOperationMask] & NSDragOperationMove )
	{
		int count;
		for( count = 0; count < [draggedItemsArray count]; count++ )
		{
			NSManagedObject *currentItemToMove = [draggedItemsArray objectAtIndex:count];
			[currentItemToMove setValue:temporaryViewPositionNum forKey:@"viewPosition"];
		}
 
		int tempRow;
		if( row == 0 )
			tempRow = -1;
		else
			tempRow = row;
 
		NSArray *startItemsArray = [self itemsWithViewPositionBetween:0 and:tempRow];
		NSArray *endItemsArray = [self itemsWithViewPositionGreaterThanOrEqualTo:row];
 
		int currentViewPosition;
 
		currentViewPosition = [self renumberViewPositionsOfItems:startItemsArray startingAt:0];
 
		currentViewPosition = [self renumberViewPositionsOfItems:draggedItemsArray startingAt:currentViewPosition];
 
		currentViewPosition = [self renumberViewPositionsOfItems:endItemsArray startingAt:currentViewPosition];
 
		return YES;
	}
	else if( [info draggingSourceOperationMask] & NSDragOperationCopy )
	{
		NSArray *copiedItemsArray = [self copyItems:draggedItemsArray];
 
		int tempRow;
		if( row == 0 )
			tempRow = -1;
		else
			tempRow = row;
 
		NSArray *startItemsArray = [self itemsWithViewPositionBetween:0 and:tempRow];
		NSArray *endItemsArray = [self itemsWithViewPositionGreaterThanOrEqualTo:row];
 
		int currentViewPosition;
 
		currentViewPosition = [self renumberViewPositionsOfItems:startItemsArray startingAt:0];
 
		currentViewPosition = [self renumberViewPositionsOfItems:copiedItemsArray startingAt:currentViewPosition];
 
		currentViewPosition = [self renumberViewPositionsOfItems:endItemsArray startingAt:currentViewPosition];
 
		return YES;
	}
 
	return NO;
}

That’s it!

You should have a fully functional drag and drop core data application.

Share
12 Responses leave one →
  1. February 21, 2009

    Great guide. I don’t know if it is my fault, but there is one problem. When I drag items, they don’t drag to the place I tell them to. How do I solve this problem?

  2. Mark Knopper permalink
    July 28, 2009

    Thanks for posting this. It was great to find specific instructions with code on how to do drag & drop for rearranging rows in a table view. I was able to use it in my app and it works fine.

  3. August 15, 2009

    Reordering rows inside a table. It sounds like something that NSTableView can do out of the box, just by setting a value to YES somewhere, so I wasn’t sure where to begin. Your article shed light on this. Thank you for writing such a verbose article.

  4. Andrew Greaney permalink
    August 29, 2009

    Being very new to Core Data, I found this tutorial very helpful.

    One problem I came across was I want to use this in a non-document based Core Data Application.

    Could you explain what changes need to be made?

    Thanks.

  5. Ellie permalink
    December 7, 2009

    Thanks for the clear post! This was very helpful.

  6. Glen Harding permalink
    January 28, 2010

    Thanks for this, it saved me a huge amount of time. But I had a small problem when moving items at the beginning of the table backwards. For example: an item at the start of the list with viewPosition=0, and moving it back to position 3 – where my table has 5 items in it.

    This would cause the viewPosition sequence to skip a number – 0,1,2,4,5. This problem in turn actually stops you from dragging an item to the end of the table. In my case with 5 items, dragging the item to the end of the list in the table would only ever put it 2nd last.

    The way I got around this was to make a simple change in tableView:acceptDrop

    change

    tempRow = row;
    to
    tempRow = row – 1;

    Hope this helps anybody else experiencing this little problem.

    But many thanks again for this post – can’t tell you how much work it saved

  7. admin permalink*
    January 29, 2010

    Thanks, Glen; I now remember that I found the bug shortly after posting the code and completely forgot to update it.

    Quite a lot going on recently, but normal service should be resumed again soon :)

  8. Tobidobi permalink
    February 12, 2010

    Great article, Tim. Thank you!

  9. Chris permalink
    November 16, 2010

    Very helpful, thanks a lot!

Trackbacks and Pingbacks

  1. Blog @ Tim Isted » Blog Archive » Sample project code now available for all posts
  2. Technoboria » links for 2009-04-16
  3. Metadata | Core Data Framework – z czym to się je?

Leave a Reply

Note: You can use basic XHTML in your comments. Your email address will never be published.

Subscribe to this comment feed via RSS