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 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
More complex cases
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