Mac OS X Reference Library Apple Developer
Search

Using Drag and Drop in Tables

The NSTableView class implements both the NSDraggingSource and NSDraggingDestination informal protocols. It manages most of the details of drag operations, sending messages to its data source when it begins or receives a drag operation. The minimum required steps for supporting drag and drop in a table are as follows:

  1. Call the registerForDraggedTypes: method of your table view to specify the types of data your table supports.

  2. Implement the tableView:writeRowsWithIndexes:toPasteboard: method in your data source to handle the beginning of a drag operation.

  3. Implement the tableView:validateDrop:proposedRow:proposedDropOperation: method in your data source to validate the drop location.

  4. Implement the tableView:acceptDrop:row:dropOperation: method in your data source to incorporate the dropped data.

Implementing the delegate methods in your data source object lets the NSTableView class know that your table supports drag and drop and also lets it know what to do with dragged or dropped data. If you omit one of these delegate methods from your data source, your table view will be unable to accept dropped data or initiate drags (depending on which method you omit). Other customizations to drag-and-drop behavior in table views are also possible but are not required.

The sections that follow provide additional information about how you implement the basic delegate methods and some specific customizations. For general information on how drag and drop works, see Drag and Drop Programming Topics for Cocoa.

Configuring Your Table View

The first step in configuring a table to support drag and drop is to tell it what data types your data source object understands. You do this by sending a registerForDraggedTypes: message to the NSTableView object. This method accepts an array of pasteboard types that your data source understands. If you want to support drag and drop operations only within your table, you can simply define a custom type. If you support data from several different sources, you might want to specify several different types.

The following example shows a sample implementation of the awakeFromNib method for a document. If you want to support drag and drop only within the table, you can simply register a custom type for your table data. If you wanted to accept other types of data, you would add the appropriate pasteboard types to the array.

Listing 1  Registering the table's supported data types

#define MyPrivateTableViewDataType @"MyPrivateTableViewDataType"
- (void)awakeFromNib
{
    [myTableView registerForDraggedTypes:
                        [NSArray arrayWithObject:MyPrivateTableViewDataType] ];
 
    // Other initialization...
}

Beginning a Drag Operation

When a drag operation begins, the table sends a tableView:writeRowsWithIndexes:toPasteboard: message to the data source. Your implementation of this method should place the data for the specified rows onto the provided pasteboard and return YES. If, for some reason, you do not want the drag operation to continue, your method should return NO.

Note: Prior to Mac OS X v10.4, table views initiated a drag by sending a tableView:writeRows:toPasteboard: message. In Mac OS X v10.4 and later, the use of this method is deprecated.

The following example shows a simple implementation of the tableView:writeRowsWithIndexes:toPasteboard: delegate method. This implementation assumes that drags and drops are confined to the table itself, so it simply copies the dragged row numbers to the pasteboard.

Listing 2  Initiating a drag from a table.

- (BOOL)tableView:(NSTableView *)tv writeRowsWithIndexes:(NSIndexSet *)rowIndexes toPasteboard:(NSPasteboard*)pboard
{
    // Copy the row numbers to the pasteboard.
    NSData *data = [NSKeyedArchiver archivedDataWithRootObject:rowIndexes];
    [pboard declareTypes:[NSArray arrayWithObject:MyPrivateTableViewDataType] owner:self];
    [pboard setData:data forType:MyPrivateTableViewDataType];
    return YES;
}

If there is a lot of data to be placed onto the pasteboard, or if there are multiple data formats possible, you may provide the data lazily using promises. To do so, just tell the pasteboard object what types you support without providing the actual data. The pasteboard object will notify you at a later time if and when the data is actually needed. See the description of the pasteboard:provideDataForType: delegate method (Objective-C) or the pasteboardProvideDataForType delegate method (Java) in the NSPasteboard class for information about fulfilling a pasteboard promise.

In Mac OS X v10.4, support was added for handling file-promised drag operations in your data source object. To support this feature in a table view, you must first promise the the data to the pasteboard using the NSFilesPromisePboardType type in your tableView:writeRowsWithIndexes:toPasteboard: method. When a destination accepts the dropped file information, NSTableView calls through to the tableView:namesOfPromisedFilesDroppedAtDestination:forDraggedRowsWithIndexes: method of your data source to provide the files. Your implementation of this method should create the files and return an array containing the filenames (without path information).

The NSTableView class only supports local drags by default. When you try to drag table rows outside of the application, the table view's draggingSourceOperationMaskForLocal: method returns NSDragOperationNone. To allow interapplication drags in Mac OS X v10.4 and later, call the setDraggingSourceOperationMask:forLocal: method of NSTableView. (If your code supports versions of Mac OS X prior to v10.4, you must subclass NSTableView instead and override draggingSourceOperationMaskForLocal: to return an appropriate value.)

Validating a Drag Operation

When a drag operation enters a table view, the table view sends a tableView:validateDrop:proposedRow:proposedDropOperation: message to its data source. If this method is not implemented or if the method returns NSDragOperationNone, the drag operation is not allowed.

The last two parameters of the tableView:validateDrop:proposedRow:proposedDropOperation: method contain the proposed row insertion point and insertion behavior (NSTableViewDropOn or NSTableViewDropAbove). You can override these values in your delegate method implementation by sending a setDropRow:dropOperation: message to the table view.

Para

- (NSDragOperation)tableView:(NSTableView*)tv validateDrop:(id <NSDraggingInfo>)info proposedRow:(NSInteger)row proposedDropOperation:(NSTableViewDropOperation)op
{
    // Add code here to validate the drop
    NSLog(@"validate Drop");
    return NSDragOperationEvery;
}

Accepting a Drop

When a validated drag operation is dropped onto a table view, the table view sends a tableView:acceptDrop:row:dropOperation: message to its data source. The data source's implementation of this method should incorporate the data from the dragging pasteboard (obtained from the acceptDrop parameter) and use the other parameters to update the table. For example, if the drag operation type (also obtained from the acceptDrop parameter) was NSDragOperationMove and the drag originated from the table, you would want to move the row from its old location to the new one.

- (BOOL)tableView:(NSTableView *)aTableView acceptDrop:(id <NSDraggingInfo>)info
            row:(NSInteger)row dropOperation:(NSTableViewDropOperation)operation
{
    NSPasteboard* pboard = [info draggingPasteboard];
    NSData* rowData = [pboard dataForType:MyPrivateTableViewDataType];
    NSIndexSet* rowIndexes = [NSKeyedUnarchiver unarchiveObjectWithData:rowData];
    NSInteger dragRow = [rowIndexes firstIndex];
 
    // Move the specified row to its new location...
}

Customizing Drag Behavior

Dragging the cursor vertically in a table view can be interpreted as an attempt either to select a range of rows or to drag one or more rows elsewhere. The default behavior of NSTableView interprets vertical drags as the beginning of a drag operation but you can change this behavior using the setVerticalMotionCanBeginDrag: method of NSTableView. Horizontal drags always begin a drag operation.

When a drag operation begins, the table view constructs an image to represent the dragged rows. The default image is a copy of the rows. If you want a different image, you must subclass NSTableView and override the dragImageForRows:event:dragImageOffset: method to return your own NSImage object.

Background Drags

Table views and outline views allow you to drag entries while your application is in the background. This only affects clients using the row/item based dragging APIs. The behavior when dragging from such a table is the same as the NSTextView implementation of dragging. If the user clicks and drags on your table while your application is not active, a drag operation is initiated. If the user simply clicks on your table, your application becomes active and the current selection does not change.




Last updated: 2006-06-28

Did this document help you? Yes It's good, but... Not helpful...