Cocoa NSTableView Drag and Drop tutorial

74: Drag rows inside a NSTableView

Problem: We have aNSTableView and want to provide the means to reorder the rows through dragging.

Drag and drop a line inside an NSTableView

Drag and drop a line inside an NSTableView example


Answer: We use Apple's tutorial on Using Drag and Drop in Tables. As is here demonstrated this works both for simple tables of text and for more complex tables of text and image.

Source code for simple text tables

// MyData.h #import <Cocoa/Cocoa.h> @interface MyData : NSObject { NSString * nsStr; } @property (assign) NSString * nsStr; +(id)newWithString:(NSString *)pStr; -(id)initWithString:(NSString *)pStr; @end // MyData.m #import "MyData.h" @implementation MyData @synthesize nsStr; +(id)newWithString:(NSString *)pStr { return [[self alloc]initWithString:pStr]; } // end newWithString -(id)initWithString:(NSString *)pStr { if ( ! (self = [super init]) ) { NSLog(@"Error MyData initWithString"); return self; } // end if self.nsStr = pStr; return self; } // end initWithString @end // MyController.h #import <Cocoa/Cocoa.h> @interface MyController : NSObjectController { NSMutableArray * nsAryOfDataValues; IBOutlet NSTableView * nsTableViewObj; NSTextFieldCell * nsTextFieldCellObj; } @property (assign) NSMutableArray * nsAryOfDataValues; @property (assign) NSTextFieldCell * nsTextFieldCellObj; @property (assign) IBOutlet NSTableView * nsTableViewObj; @end // MyController.m #import "MyController.h" #import "MyData.h" @implementation MyController @synthesize nsAryOfDataValues; @synthesize nsTextFieldCellObj; @synthesize nsTableViewObj; #define MyPrivateTableViewDataType @"MyPrivateTableViewDataType" - (void) awakeFromNib { nsAryOfDataValues = [[NSMutableArray alloc]init]; [nsAryOfDataValues addObject: [MyData newWithString:@"Fuzzy Wuzzy was a bear"]]; [nsAryOfDataValues addObject: [MyData newWithString:@"Fuzzy Wuzzy had no hair"]]; [nsAryOfDataValues addObject: [MyData newWithString:@"So he wasn't Fuzzy Wuzzy?"]]; [nsTableViewObj registerForDraggedTypes: [NSArray arrayWithObject:MyPrivateTableViewDataType]]; } // end awakeFromNib // these are called by the table(s) - (int)numberOfRowsInTableView:(NSTableView *)tableView { if (tableView == nsTableViewObj) { return [nsAryOfDataValues count]; } // end if return 0; } // end numberOfRowsInTableView - (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(int)row { if (tableView == nsTableViewObj) { MyData * zData = [nsAryOfDataValues objectAtIndex:row]; return zData.nsStr; } // end if return NULL; } // end tableView:objectValueForTableColumn:tableColumn - (void)tableView:(NSTableView *)aTableView setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex { if (aTableView == nsTableViewObj) { MyData * zData = [nsAryOfDataValues objectAtIndex:rowIndex]; zData.nsStr = (NSString *)anObject; [nsAryOfDataValues replaceObjectAtIndex:rowIndex withObject:zData]; } // end if } // end tableView:setObjectValue:forTableColumn:row: // drag operation stuff - (BOOL)tableView:(NSTableView *)tv writeRowsWithIndexes:(NSIndexSet *)rowIndexes toPasteboard:(NSPasteboard*)pboard { // Copy the row numbers to the pasteboard. NSData *zNSIndexSetData = [NSKeyedArchiver archivedDataWithRootObject:rowIndexes]; [pboard declareTypes:[NSArray arrayWithObject:MyPrivateTableViewDataType] owner:self]; [pboard setData:zNSIndexSetData forType:MyPrivateTableViewDataType]; return YES; } - (NSDragOperation)tableView:(NSTableView*)tv validateDrop:(id )info proposedRow:(NSInteger)row proposedDropOperation:(NSTableViewDropOperation)op { // Add code here to validate the drop return NSDragOperationEvery; } - (BOOL)tableView:(NSTableView *)aTableView acceptDrop:(id )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... // if we remove a row then everything moves down by one // so do an insert prior to the delete // --- depends which way were moving the data!!! if (dragRow < row) { [nsAryOfDataValues insertObject: [nsAryOfDataValues objectAtIndex:dragRow] atIndex:row]; [nsAryOfDataValues removeObjectAtIndex:dragRow]; [self.nsTableViewObj noteNumberOfRowsChanged]; [self.nsTableViewObj reloadData]; return YES; } // end if MyData * zData = [nsAryOfDataValues objectAtIndex:dragRow]; [nsAryOfDataValues removeObjectAtIndex:dragRow]; [nsAryOfDataValues insertObject:zData atIndex:row]; [self.nsTableViewObj noteNumberOfRowsChanged]; [self.nsTableViewObj reloadData]; return YES; } @end

More complex cases

Drag and drop a row inside an NSTableView with ATImageTextCell elements

Drag and drop a line inside an NSTableView with ATImageTextCell elements

This same code also works when applied, virtually as is, to more complicated tables such as that shown in example 71: Display an NSTextfieldCell containing text and an image within a NSTableView

Here is the whole of the updated MyControl.m class for that example. As may be seen the previous code has been transposed unaltered other than for a few variable names.

// MyControl.m ##35;import "MyControl.h" ##35;import "MyData.h" ##35;import "MyParentImageText.h" @implementation MyControl @synthesize nsMutaryOfMyData; @synthesize myParentImageTextObj; @synthesize nsTableViewObj; // DRAG STUFF ##35;define MyPrivateTableViewDataType @"MyPrivateTableViewDataType" // END DRAG STUFF - (void) awakeFromNib { // self.nsIntSelectedRow = -1; self.nsMutaryOfMyData = [[NSMutableArray alloc]init]; [self.nsMutaryOfMyData addObject:[[MyData alloc] initWithParentImagePath:@"../../../Lucia.tif" parentText:@"Lucia, dog, peacock and Galapagos turtle" childText:@"watercolour and body colour"]]; [self.nsMutaryOfMyData addObject:[[MyData alloc] initWithParentImagePath:@"../../../BellaSeals.tif" parentText:@"Bella's Dream (detail): Bella, Lucia and the seals" childText:@"watercolour and body colour"]]; [self.nsMutaryOfMyData addObject:[[MyData alloc] initWithParentImagePath:@"../../../BellaPanda.tif" parentText:@"Bella's Dream (detail): Bella, Lucia and Panda" childText:@"watercolour and body colour"]]; [self.nsMutaryOfMyData addObject:[[MyData alloc] initWithParentImagePath:@"../../../landBogay.tif" parentText:@"View of Bogay" childText:@"Oil on canvass"]]; [self.nsMutaryOfMyData addObject:[[MyData alloc] initWithParentImagePath:@"../../../landRiverRoeSpring.tif" parentText:@"The River Roe in Spring" childText:@"Oil on canvass"]]; [self.nsMutaryOfMyData addObject:[[MyData alloc] initWithParentImagePath:@"../../../oliveHarvest.tif" parentText:@"The Olive Harvest" childText:@"Ceramic tiles"]]; self.myParentImageTextObj = [[MyParentImageText alloc] init]; self.myParentImageTextObj.nsStringParentText = [[self.nsMutaryOfMyData objectAtIndex:0]nsStringParentText]; self.myParentImageTextObj.nsStringChildText = [[self.nsMutaryOfMyData objectAtIndex:0]nsStringChildText]; self.myParentImageTextObj.nsParentImageObj = [[self.nsMutaryOfMyData objectAtIndex:0]nsParentImageObj]; [self.myParentImageTextObj setEditable: YES]; NSTableColumn* zTableColumnObj = [[self.nsTableViewObj tableColumns] objectAtIndex:0]; [zTableColumnObj setDataCell: self.myParentImageTextObj]; // DRAG STUFF [self.nsTableViewObj registerForDraggedTypes: [NSArray arrayWithObject:MyPrivateTableViewDataType] ]; // END DRAG STUFF } // end awakeFromNib // NSTableView delegate methods - (NSInteger)numberOfRowsInTableView:(NSTableView *)pTableView { return [nsMutaryOfMyData count]; } // end numberOfRowsInTableView - (id)tableView:(NSTableView *)pTableView objectValueForTableColumn:(NSTableColumn *)pTableColumn row:(int)pRow { MyData * zMyDataObj = [self.nsMutaryOfMyData objectAtIndex:pRow]; return zMyDataObj.nsStringParentText; } // end tableView:objectValueForTableColumn:tableColumn // this is the delegate method that allows you to put data into your cell - (void)tableView:(NSTableView *)tableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)pRow { MyData * zMyDataObj = [self.nsMutaryOfMyData objectAtIndex:pRow]; MyParentImageText * zMyCell= (MyParentImageText *)cell; zMyCell.nsStringParentText = zMyDataObj.nsStringParentText; zMyCell.nsStringChildText = zMyDataObj.nsStringChildText; zMyCell.nsParentImageObj = zMyDataObj.nsParentImageObj; // we now need to get zMyCell to crete the other cells. // ultimately this will be done by the data setter methods but for now // it is useful to keep the mechanism clearly visible [zMyCell createCells]; } // end tableView:willDisplayCell:forTableColumn:row: // if this is not here we crash - (NSCell *)tableView:(NSTableView *)pTableView dataCellForTableColumn:(NSTableColumn *)pTableColumn row:(NSInteger)pRow { return self.myParentImageTextObj; } // end tableView:dataCellForTableColumn:row: // this is the routine that returns cell data // (an edited string) back after editing - (void)tableView:(NSTableView *)aTableView setObjectValue:anObject forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)pRow { MyData * zMyDataObj = [self.nsMutaryOfMyData objectAtIndex:pRow]; NSLog(@"setObjectValue string = %@",(NSString *)anObject); zMyDataObj.nsStringParentText = (NSString *)anObject; //[aTableView reloadData]; } // end tableView:setObjectValue:forTableColumn:row: - (IBAction)tableViewSelected:(id)sender { NSLog(@"user clicked row %d",[self.nsTableViewObj selectedRow]); } // end tableViewSelected - (IBAction)addAtSelectedRow:(id)pId { [[self.nsTableViewObj window] makeFirstResponder:[self.nsTableViewObj window]]; NSInteger zSelectedRow = [self.nsTableViewObj selectedRow]; if ( zSelectedRow < 0) { return; } // end if NSParameterAssert(zSelectedRow < [self.nsMutaryOfMyData count]); [nsMutaryOfMyData insertObject:[[MyData alloc] initWithParentImagePath:@"../../../anghiari.tif" parentText:@"Copy after Ruben's copy after Leonardo: The Battle of Anghiari" childText:@"Oil on canvass"] atIndex:zSelectedRow]; [self.nsTableViewObj noteNumberOfRowsChanged]; [self.nsTableViewObj reloadData]; } // end addAtSelectedRow - (IBAction)addToEndOfTable:(id)pId { NSLog(@"addToEndOfTable"); [nsMutaryOfMyData addObject:[[MyData alloc] initWithParentImagePath:@"../../../BellaElephants.tif" parentText:@"Bella's Dream (detail): Bella and Elephants" childText:@"Watercolour with body colour"]]; [self.nsTableViewObj noteNumberOfRowsChanged]; [self.nsTableViewObj reloadData]; } // end addToEndOfTable - (IBAction)removeCellAtSelectedRow:(id)sender { if ([self.nsTableViewObj selectedRow] < 0 || ([self.nsTableViewObj selectedRow] >= [nsMutaryOfMyData count])) { return; } // end if [nsMutaryOfMyData removeObjectAtIndex:[self.nsTableViewObj selectedRow]]; [self.nsTableViewObj noteNumberOfRowsChanged]; [self.nsTableViewObj reloadData]; } // end removeCellAtSelectedRow // DRAG STUFF - (BOOL)tableView:(NSTableView *)tv writeRowsWithIndexes:(NSIndexSet *)rowIndexes toPasteboard:(NSPasteboard*)pboard { // Copy the row numbers to the pasteboard. NSData *zNSIndexSetData = [NSKeyedArchiver archivedDataWithRootObject:rowIndexes]; [pboard declareTypes:[NSArray arrayWithObject:MyPrivateTableViewDataType] owner:self]; [pboard setData:zNSIndexSetData forType:MyPrivateTableViewDataType]; return YES; } - (NSDragOperation)tableView:(NSTableView*)tv validateDrop:(id )info proposedRow:(NSInteger)row proposedDropOperation:(NSTableViewDropOperation)op { // Add code here to validate the drop //NSLog(@"validate Drop"); return NSDragOperationEvery; } - (BOOL)tableView:(NSTableView *)aTableView acceptDrop:(id )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... // if we remove a row then everything moves down by one // so do an insert prior to the delete // --- depends which way we're moving the data!!! if (dragRow < row) { [self.nsMutaryOfMyData insertObject: [self.nsMutaryOfMyData objectAtIndex:dragRow] atIndex:row]; [self.nsMutaryOfMyData removeObjectAtIndex:dragRow]; [self.nsTableViewObj noteNumberOfRowsChanged]; [self.nsTableViewObj reloadData]; return YES; } // end if MyData * zData = [self.nsMutaryOfMyData objectAtIndex:dragRow]; [self.nsMutaryOfMyData removeObjectAtIndex:dragRow]; [self.nsMutaryOfMyData insertObject:zData atIndex:row]; [self.nsTableViewObj noteNumberOfRowsChanged]; [self.nsTableViewObj reloadData]; return YES; } // END DRAG STUFF @end

Example source code



Please send me your comments

If you include your e-mail I may reply!  

Page last modified: 18:58 Sunday 12th. May 2013

Julius Guzy

Paintings & Drawings

  • view of the Donegal Shop from the bottom of Shipquay Street, Derry

animatedPaint