NSPasteboard type for NSManagedObject - objective-c

I need to drag a reference to an NSManagedObject between two table views of my application. What's the preferred NSPasteboard type to store a reference to an NSManagedObject?
My current solution is to store the URIRepresentation of the object's NSManagedObjectID in a NSPasteboardTypeString. I suspect there's a more elegant solution out there.

There is no standard type for all model objects since your model objects and how they're handled are unique to your application. If there was one pasteboard type for all then there'd be no telling them apart. Your own custom object should have its own drag type.
Just use a string that makes sense (maybe a #define so you can find it with auto-complete in Xcode) like "MyObjectPboardType" that resolves to "com.yourcompany.yourapp.yourobjecttype".
Use NSPasteboard's -declareTypes:owner: to declare your new type, then use -setString:forType: or one of the other -set?:forType: methods to set the information for your object's type. In your case, the use of the object ID is a perfectly acceptable identifier. Just remember managed objects' object IDs change when they're new versus persisted.

If you are dragging within tables in the same application you might as well put in pasteboard the rowIndexes (indexPaths in case you are dragging from an outlineView) of the objects in the tableView (outlineView). This might as well spare you from some unneeded CoreData access if the dataSource of the tableViews are NSArrayController (NSTreeController for outlineView).
You can then easily retrieve the dragged objects when accepting the drop since the ‘info‘ object passed to both methods ‘tableView:validateDrop:proposedRow: proposedDropOperation:‘ and to ‘tableView:acceptDrop:row:dropOperation:‘ will have a reference to the tableView originating the drag under ‘draggingSource‘ key path.
Here's a simple implementation:
extern NSString *const kMyLocalDragType = #"com.whatever.localDragType";
#implementation MyArrayControllerDataSource
.
.
.
#pragma mark - NSTableViewDataSource (Drag & Drop)
+ (NSArray *)dragTypes {
// convenience method returning all class's supported dragTypes
return #[kMyLocalDragType];
}
- (BOOL)tableView:(NSTableView *)tableView writeRowsWithIndexes:(NSIndexSet *)rowIndexes toPasteboard:(NSPasteboard *)pboard {
[pboard declareTypes:[[self class] dragTypes] owner:self];
for (NSString *aDragType in [[self class] dragTypes]) {
if (aDragType == kMyLocalDragType) {
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:rowIndexes]; // we are supporting drag&drop of multiple items selected
[pboard setData:data forType:aDragType];
}
.
. // logic for other dragTypes
.
}
return YES;
}
- (NSDragOperation)tableView:(NSTableView *)tableView validateDrop:(id<NSDraggingInfo>)info proposedRow:(NSInteger)row proposedDropOperation:(NSTableViewDropOperation)dropOperation {
NSArray *dragTypes = [info draggingPasteboard] types];
for (id aDragType in dragTypes) {
if (aDragType == kMyLocalDragType) {
return NSDragOperationCopy;
}
}
.
.// Other logic for accepting drops/affect drop operation
.
}
- (BOOL)tableView:(NSTableView *)tableView acceptDrop:(id<NSDraggingInfo>)info row:(NSInteger)row dropOperation:(NSTableViewDropOperation)dropOperation {
if ([info draggingPasteboard] types] containsObject:kMyLocalDragType]) {
// Retrieve the index set from the pasteboard:
NSData *data = [[info draggingPasteboard] dataForType:kMyLocalDragType];
NSIndexSet *rowIndexes = [NSKeyedUnarchiver unarchiveObjectWithData:data];
NSArray *droppedObjects = [self retrieveFromTableView:tableView objectsAtRows:rowIndexes];
// droppedObjects contains dragged and dropped objects, do what you
// need to do with them, then add them to this dataSource:
[self.content insertObjects:droppedObjects];
[tableView reloadData];
[tableView deselectAll:nil];
return YES;
}
.
. // other logic for accepting drops of other dragTypes supported.
.
}
#pragma mark - Helpers
- (NSArray <NSManagedObject *> *)retrieveFromTableView:(NSTableView *)tableView objectsAtRowIndexes:(NSIndexSet *)rowIndexes {
id dataSource = [tableView dataSource];
if ([dataSource respondsToSelector:#selector(content)]) {
if ([dataSource.content respondsToSelector:#selector(objectsAtIndexes:)]) {
return [datasource content] objectsAtIndexes:rowIndexes];
}
}
return #[]; //We return an empty array in case introspection check failed
}

Related

How to keep data in a NSMutableArray

AAA.m:
- (void)keepCurrentArray:(id)object
{
_currentTest=[[NSMutableArray alloc]init];
[_currentTest addObject:#"one"];
[_currentTest addObject:#"two"];
[_currentTest addObject:object];
NSLog(#"My Array is:%#",_currentTest);
}
Class BBB.m is passing objects to class AAA.
Right now if i'm passing X to the above method so the array will be: one,two,X . Then i'll send it Y and the array will be one,two,Y instead of what i want to accomplish: one,two,x,one,two,y.
Is that because I'm alloc and init _currentTest every time? How can I solve it?
Update:
I had a few suggestions on how to solve this and none of them worked for me. I've created a new project with just the code in the answers and i'm still getting the same result when I try to add the second object i get: one, two, test instead of one,two,test,one,two,test
Yes, it's because that you're alloc and init-ing every time you run that method. Instead, put _currentTest = [[NSMutableArray alloc] init]; in AAA.m's init method.
AAA.m
-(id)init
{
if ((self = [super init]))
_currentTest = [[NSMutableArray alloc] init];
return self;
}
- (void)keepCurrentArray:(id)object
{
[_currentTest addObject:#"one"];
[_currentTest addObject:#"two"];
[_currentTest addObject:object];
NSLog(#"My Array is:%#",_currentTest);
}
_currentTest=[[NSMutableArray alloc]init]; in a method is never a good thing!!!
As per naming convention it seems to be a property to the AAA Class. So for property, the alloc+init should be either in init or awakeFromNib. So that if is initialized just once.
However in some situations init is called more than once then your previous values are lost and new set are added.
So what you can do is make another class and put this _currentTest Array there and make it static and use it here. I hope this will work fine. And make sure in the init method of that class it is initialized just once, as :
//**this is not compiled and checked may contains typo and errors**
#implementation Storage
static NSMutableArray *yourStaticArray;
-(id)init{
self = [super init];
if (self) {
if (!yourStaticArray) {
yourStaticArray=[NSMutableArray new];
}
}
return self;
}
-(void)addYourStaticArray:(NSString *)val{
[yourStaticArray addObject:val];
}
-(NSArray *)yourStaticArray {
return yourStaticArray ;
}
#end
Well you need to have a property for that _currentTest if you want to be able to keep it around between method call.
Put this in your .h file
#property (nonatomic, copy) NSMutableArray * currentTest;
And this in hour .m file
- (NSMutableArray *)currentTest
{
if (!_currentTest)
_currentTest = [[NSMutableArray alloc] initWithCapacity:11];
return _currentTest;
}
- (void)keepCurrentArray:(id)object
{
[self.currentTest addObject:#"one"];
[self.currentTest addObject:#"two"];
[self.currentTest addObject:object];
NSLog(#"My Array is:%#", self.currentTest);
}
I Just try the code you've put on drop box and it's working exactly as it is suppose to, the array keeps it's value and everything,
BUT
Exactly as it is suppose to is not what you are trying to achieve
Your problem is not in AAA.m, your problem is in BBB.m
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
ViewController *kios = [ViewController new];
[kios keepCurrentArray:#"Test"];
[kios keepCurrentArray:#"Test2"];
}
I took the liberty of adding the #"test2" to the code you've send. If you run it you will see that your array still exist when the second call is made.
The REAL problem here is that you are creating a NEW ViewController each time. A brand new one, it is normal that it is empty (clean), it's a new one.
If I buy a note pad monday and fill it up, I don't expect when I'm buying an other one on friday to be already fill with the stuff I've wrote on monday in the previous one.
But this is exactly that behaviour that you are expecting from your ViewController.
You need to store your NSMutableArray in an other object that doesn't
get destroy and created over and over again.
This is happening because you are creating a new array every time that your method is called. Basically, you need to see if it has already been created, and only create it if needed. You can change your method to:
- (void)keepCurrentArray:(id)object
{
if (!_currentTest)
{
_currentTest=[[NSMutableArray alloc]init];
}
[_currentTest addObject:#"one"];
[_currentTest addObject:#"two"];
[_currentTest addObject:object];
NSLog(#"My Array is:%#",_currentTest);
}
EDIT:
In addition to the above problem, you also have this code which needs to be corrected (comments removed):
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
ViewController *kios = [ViewController new];
[kios keepCurrentArray:#"Test"];
}
This code creates a new instance of ViewController every time that you click on a row in the table. Because you are creating a new instance instead of reusing the old one, you start with an empty array each time. In order to keep adding to the same array, you need to keep using the same view controller.
In order to do this, you need to add a declared property to your .h file, similar to your currentTest declared property:
#property (strong,nonatomic) ViewController *kios;
Then, change your action so that you only create a new view controller if needed (the first time) and then reuses it after that:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (!_kios)
{
_kios = [ViewController new];
}
[_kios keepCurrentArray:#"Test"];
}

TableView doesn't allow for 'section' in dictionary

I want to loop through a list of json items to use in my sectioned tableView. For this I would like to restructure the data to have a section->array setup, where array contains an array of sessions.
First of all, I don't know if this is the preferred way to go, there may be easier ways. I keep getting the error that I am not allowed to use 'section' as an identifier in the dictionary. Moreover, when I use something else than a 'section' the dictionary keeps getting overridden.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSString *day = _json[#"days"][3];
NSString *key;
NSUInteger count = 0;
NSMutableArray *sessionList = [[NSMutableArray alloc] init];
NSArray *timeslotsSorted = [[_json[#"schedule"][day] allKeys]
sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
NSArray *locationsSorted = [[_json[#"schedule"][day][timeslotsSorted[section]] allKeys]
sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
for (key in locationsSorted) {
NSDictionary *temp = _json[#"schedule"][day][timeslotsSorted[section]][key];
if ([temp isKindOfClass:[NSDictionary class]]) {
[sessionList addObject:temp[#"title"]]; //test array
count++;
}
}
_sessionDict = #{
section: sessionList
};
return count;
}
You are doing all the work to build your data structure in the wrong place. Lets say there are 10 sections in your data. This will call the tableView: numberOfRowsInSection method 10 times which makes this a pretty inefficient place to do much work. You will also have to implement the method that returns the number of sections to show, and the method to display each individual row.
I would build my data structures in the viewWillLoad method and then store it locallaly and reuse it in all the tableView methods.
First, this is what NSInteger is:
typedef int NSInteger;
You must wrap it into an object. Something like:
[NSNumber numberWithInteger:section]
And than add it to your dictionary.
i don't really know that your timeslotsSorted and locationsSorted contain right items, but lets say they do. I would recommend you to have this sorting before - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section and - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView; are called.
Lets say, you received JSON, so you have to parse it as you do, and then call [tableView reloadData] or reload visible cells with animations.
and then your tableView data source methods will be called and you will do something like:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return [sessionList count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSString* key = [self.sessionList objectAtIndex:section];
return [[self.secions objectForKey:key] count];
}
and don't forget to make strong properties for self.sections and self.sessionList

primitive accessors in this example

Could someone help me understand the primitive accessors with this example : i don't understand what is automatically set and the order of those methods :
1.after a person is created, is willSave the first method called? (i guess so, because save: is called after we create a person with insertNewObjectForEntityForName )
2.in RootViewController (the second chunk of code), we then call the getter of eyeColor with : person.eyeColor :
a) in eyeColor, we call : [self eyeColorData] ,
b) but setPrimitiveEyeColorData is in willSave, which is accessible only if primitiveEyeColor exists,
c) but setPrimitiveEyeColor is in eyeColor and only called if [self eyeColorData] exists. So, i'm a bit confused with this code, could someone help me?
here's the code about eyeColor and eyeColorData :
#dynamic eyeColorData;
#dynamic eyeColor;
#interface AWPerson (PrimitiveAccessors)
- (UIColor *)primitiveEyeColor;
- (void)setPrimitiveEyeColor:(UIColor *)value;
- (NSData *)primitiveEyeColorData;
- (void)setPrimitiveEyeColorData:(NSData *)value;
#end
+ (id)personInManagedObjectContext:(NSManagedObjectContext *)moc {
return [NSEntityDescription
insertNewObjectForEntityForName:#"Person"
inManagedObjectContext:moc];
}
+ (id)randomPersonInManagedObjectContext:(NSManagedObjectContext *)moc {
AWPerson *randomPerson = [self personInManagedObjectContext:moc];
//...
randomPerson.eyeColor = [self randomColor]; //setter eyeColor
return randomPerson;
}
+ (UIColor *)randomColor {
static NSArray *colorsArray = nil;
if( !colorsArray ) {
colorsArray = [[NSArray alloc] initWithObjects:
[UIColor lightGrayColor],
[UIColor blueColor],
[UIColor greenColor], nil];
}
int randomIndex = arc4random() % [colorsArray count];
return [colorsArray objectAtIndex:randomIndex];
}
- (void)willSave {
UIColor *color = [self primitiveEyeColor];
if( color ) {
[self setPrimitiveEyeColorData:
[NSKeyedArchiver archivedDataWithRootObject:color]];
} else {
[self setPrimitiveEyeColorData:nil];
}
[super willSave];
}
- (UIColor *)eyeColor {
[self willAccessValueForKey:#"eyeColor"];
UIColor *tmpValue = [self primitiveEyeColor];
[self didAccessValueForKey:#"eyeColor"];
if( tmpValue ) return tmpValue;
NSData *colorData = [self eyeColorData];
if( !colorData ) return nil;
tmpValue = [NSKeyedUnarchiver unarchiveObjectWithData:colorData];
[self setPrimitiveEyeColor:tmpValue];
return tmpValue;
}
in RootViewController :
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
AWPerson *person = [[self fetchedResultsController] objectAtIndexPath:indexPath];
[cell setBackgroundColor:person.eyeColor];
}
Thanks
EDIT - Added info on willSave
To answer your first question, willSave is called whenever the object is saved (using the save method). So the first method called will be one of the class methods (used to create the object) or init and then, since you said that the object is saved just after it is created, willSave gets called.
I think the key to understanding this is to realize that eyeColor, primitiveEyeColor, and their setters are all ultimately interacting with the same variable in memory (the iVar named eyeColor). The difference is whether or not the code in the setter/getter (in this case the - (UIColor *)eyeColor { function) is called.
There are just a few different ways to interact with it:
[self primitiveEyeColor]; - This reads the value of the iVar directly.
[self setPrimitiveEyeColor:tmpValue]; - This sets the value of the iVar directly.
[self eyeColor] - This calls the - (UIColor *)eyeColor method in your class (which should ultimately retrieve the iVar or a representation of it).
[self setEyeColor:value] - This calls the - (void)setEyeColor:(UIColor *)newColor method in your class. Note that in this case it doesn't exist so it simply calls the primitive method (and does the KVO magic).
In this particular code, they are using a "non-standard persistent attribute" because NSManagedObject does not support UIColor's. Read about it here.
EDIT 2
To answer your other questions:
a) The color in randomPerson.eyeColor = [self randomColor] is
accessible with [self primitiveEyeColor] (in willSave)?
Yes, once eyeColor is set (either via the setEyeColor method or the setPrimitiveEyeColor method), you can read it from primitiveEyeColor and it will return the same value.
Note that once it is set, eyeColor and primitiveEyeColor return the same value and can be called from anywhere in your class (not just willSave).
b) So if [self primitiveEyeColor] != nil : in eyeColor, the line :
if( tmpValue ) return tmpValue; should therefore always be true...
when can we unarchive eyeColorData if UIColor *tmpValue = [self
primitiveEyeColor] is always returned in -(UIColor *)eyeColor?
This method only looks at eyeColorData (which was stored during the last call to willSave) if eyeColor is nil. This is an optimization because we could skip all of this and just unarchive eyeColorData every time if we wanted to. In this case, once a value is unarchived or set to a new value, it always stores that value and returns it so that we don't have to call unarchive again.
Also, there is really what I believe to be an error here (although it could be by design). Let's say that we perform the following steps:
Set eyeColor to a random color (let's say blue).
save the object.
Set eyeColor to nil
Now, if you check the color using [self eyeColor] it will see that primitiveEyeColor is nil and unarchive eyeColorData again, therefore returning the blue color that was stored previously. You should probably be over-riding the set function so that it sets eyeColorData to nil when eyeColor is set to nil. That way, checking the value of eyeColor after setting it to nil will return nil as expected.

How to passing a NSMutableArray to another ViewController class

I have created NSMutale Array in "HeroListViewController". I want use it in another viewController which is MapTutorialViewController. I tried like this.
in HeroListViewController.h
MapTutorialViewController *maptutorialcontroller;
NSMutableArray *listData;
set properties and synthesize them correctly
in HeroListViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
listData = [[NSMutableArray alloc] init];
}
- (UITableViewCell *)tableView:(UITableView *)theTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *HeroTableViewCell = #"HeroTableViewCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:HeroTableViewCell];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:HeroTableViewCell] autorelease];
}
NSManagedObject *oneHero = [self.fetchedResultsController objectAtIndexPath:indexPath];
NSInteger tab = [tabBar.items indexOfObject:tabBar.selectedItem];
switch (tab) {
case kByName:
cell.textLabel.text = [oneHero valueForKey:#"name"];
cell.detailTextLabel.text = [oneHero valueForKey:#"secretIdentity"];
break;
case kBySecretIdentity:
cell.detailTextLabel.text = [oneHero valueForKey:#"name"];
cell.textLabel.text = [oneHero valueForKey:#"secretIdentity"];
default:
break;
}
[listData addObject: [oneHero valueForKey:#"secretIdentity"]];
count=[listData count];
printf("No of items of listData:%u\n", count);
if(maptutorialcontroller==nil){
maptutorialcontroller= [[MapTutorialViewController alloc]initWithNibName:#"MapTutorialViewController" bundle:nil];
maptutorialcontroller.secondarray=listData;
}
count=[maptutorialcontroller.secondarray count];
printf("No of items of seconarray :%u\n", count);
return cell;
}
OUTPUTS : No of items of listData:3
No of items of seconarray :3 // both are correct
BUT the the problem I have, when I try to use the secondarray in "MapTutorialViewController" like this,
in MapTutorialViewController.h
HeroListViewController *heroviewcontroller;
NSMutableArray *secondarray;
set properties and synthesize them correctly
in MapTutorialViewController.m
- (void)viewDidLoad
{
heroviewcontroller = [[HeroListViewController alloc]initWithNibName:#"HeroListViewController" bundle:nil];
self.secondarray=[heroviewcontroller.listData mutableCopy];
//secondarray= heroviewcontroller.listData;
int count;
count = [secondarray count];
//
printf("No of items of secondarray from MapTutorialViewContriller :%u\n", count);
}
OUTPUT : No of items of secondarray from MapTutorialViewContriller :0
Why it is 0
whats the wrong with my code, please help me
Example
firstviewcontroller .h file
before #interface
use #class secondViewcontroller;
declare this inside of #interface with
secondViewcontroller *sVC;
then in firstViewController.m file
before #implementation
#import "secondViewcontroller.h"
then
-------------------
secondVC.h file
#interface inside declare this
say NSMutableArray *secondarray;
and sythasize them.
-------------------
after this
in firstViewcontroller.h viewdidload create this sVC by alloc and initwithnibname then
sVC.secondArray=urfirstArray;
now while u push this sVC controller to navigation controller u can nslog this array in viewdidload.
This would only work if you create and fill the mutable array in the init method.
You should look into delegation and/or notification.
How is that array being created within HeroListViewController? In this method, you are creating a NEW instance of HeroListViewController and trying to get a property from it. If you already have a HeroListViewController in memory, this is completely wrong.
Make a property on the class for this viewDidLoad method. It should be of type NSMutableArray. When you allocate and initialize this class, call [set myArray:heroListArray] on it from HeroListViewController. That should give you access to it.
I'm assuming that you have a view containing this new view and the hero list view. If that is the case, then you could create a property in the new view like so:
#property (nonatomic,retain)HeroListViewController *heroListViewController;
and then set it equal to the heroList from the outside:
newView.heroListViewController = HeroListViewController;
The main problem with your code at the moment is that you're creating a new instance of HeroListViewController by using alloc init, and you're not accessing the same thing. By setting the new view's heroListViewController property, you can get access to the correct viewController.
Finally, in viewDidLoad of the new view - I'd actually put the code in viewWillAppear:(BOOL)Animated - you can put code to match the arrays.
Note that this whole way of doing it is messy and could be better done with a singleton class if you need access to an array in multiple places. The above will help you get it working quick, but if you want a really clean fix, go here: http://www.iphonedevsdk.com/forum/iphone-sdk-tutorials/24135-singleton-classes.html

Arrow keys with NSTableView

Is it possible to navigate an NSTableView's editable cell around the NSTableView using arrow keys and enter/tab? For example, I want to make it feel more like a spreadsheet.
The users of this application are expected to edit quite a lot of cells (but not all of them), and I think it would be easier to do so if they didn't have to double-click on each cell.
In Sequel Pro we used a different (and in my eyes simpler) method: We implemented control:textView:doCommandBySelector: in the delegate of the TableView. This method is hard to find -- it can be found in the NSControlTextEditingDelegate Protocol Reference. (Remember that NSTableView is a subclass of NSControl)
Long story short, here's what we came up with (we didn't override left/right arrow keys, as those are used to navigate within the cell. We use Tab to go left/right)
Please note that this is just a snippet from the Sequel Pro source code, and does not work as is
- (BOOL)control:(NSControl *)control textView:(NSTextView *)textView doCommandBySelector:(SEL)command
{
NSUInteger row, column;
row = [tableView editedRow];
column = [tableView editedColumn];
// Trap down arrow key
if ( [textView methodForSelector:command] == [textView methodForSelector:#selector(moveDown:)] )
{
NSUInteger newRow = row+1;
if (newRow>=numRows) return TRUE; //check if we're already at the end of the list
if (column>= numColumns) return TRUE; //the column count could change
[tableContentView selectRowIndexes:[NSIndexSet indexSetWithIndex:newRow] byExtendingSelection:NO];
[tableContentView editColumn:column row:newRow withEvent:nil select:YES];
return TRUE;
}
// Trap up arrow key
else if ( [textView methodForSelector:command] == [textView methodForSelector:#selector(moveUp:)] )
{
if (row==0) return TRUE; //already at the beginning of the list
NSUInteger newRow = row-1;
if (newRow>=numRows) return TRUE;
if (column>= numColumns) return TRUE;
[tableContentView selectRowIndexes:[NSIndexSet indexSetWithIndex:newRow] byExtendingSelection:NO];
[tableContentView editColumn:column row:newRow withEvent:nil select:YES];
return TRUE;
}
Well it isn't easy but I managed to do it without having to use RRSpreadSheet or even another control. Here's what you have to do:
Create a subclass of NSTextView, this will be the field editor. For this example the name MyFieldEditorClass will be used and myFieldEditor will refer to an instance of this class.
Add a method to MyFieldEditorClass called "- (void) setLastKnownColumn:(unsigned)aCol andRow:(unsigned) aRow" or something similar, and have it save both the input parameter values somewhere.
Add another method called "setTableView:" and have it save the NSTableView object somewhere, or unless there is another way to get the NSTableView object from the field editor, use that.
Add another method called - (void) keyDown:(NSEvent *) event. This is actually overriding the NSResponder's keyDown:. The source code should be (be aware that StackOverflow's MarkDown is changing < and > to < and >):
- (void) keyDown:(NSEvent *) event
{
unsigned newRow = row, newCol = column;
switch ([event keyCode])
{
case 126: // Up
if (row)
newRow = row - 1;
break;
case 125: // Down
if (row < [theTable numberOfRows] - 1)
newRow = row + 1;
break;
case 123: // Left
if (column > 1)
newCol = column - 1;
break;
case 124: // Right
if (column < [theTable numberOfColumns] - 1)
newCol = column + 1;
break;
default:
[super keyDown:event];
return;
}
[theTable selectRow:newRow byExtendingSelection:NO];
[theTable editColumn:newCol row:newRow withEvent:nil select:YES];
row = newRow;
column = newCol;
}
Give the NSTableView in your nib a delegate, and in the delegate add the method:
- (BOOL) tableView:(NSTableView *)aTableView shouldEditColumn:(NSTableColumn *) aCol row:aRow
{
if ([aTableView isEqual:TheTableViewYouWantToChangeBehaviour])
[myFieldEditor setLastKnownColumn:[[aTableView tableColumns] indexOfObject:aCol] andRow:aRow];
return YES;
}
Finally, give the Table View's main window a delegate and add the method:
- (id) windowWillReturnFieldEditor:(NSWindow *) aWindow toObject:(id) anObject
{
if ([anObject isEqual:TheTableViewYouWantToChangeBehaviour])
{
if (!myFieldEditor)
{
myFieldEditor = [[MyFieldEditorClass alloc] init];
[myFieldEditor setTableView:anObject];
}
return myFieldEditor;
}
else
{
return nil;
}
}
Run the program and give it a go!
Rather than forcing NSTableView to do something it wasn't designed for, you may want to look at using something designed for this purpose. I've got an open source spreadsheet control which may do what you need, or you may at least be able to extend it to do what you need: MBTableGrid
I wanted to reply to the answers here but the reply button seems to be missing so I'm forced to proved an answer when I really just want to ask a question about the replies.
Anyway, I've seen a few answers for overriding the -keyDown event of the table view that say to subclass the TableView but according to every Objective-C book I've read so far, and several Apple training videos, you should very rarely if ever subclass one of the core classes. In fact every single one of them makes the point that C programmers have a fascination with subclassing and that's not how Objective-C works; that Objective-C is all about helpers and delegates not subclassing.
So, should I just ignore any of the responses that say to subclass as this seems to be in direct contradiction to the precepts of Objective-C?
--- Edit ---
I found something that worked without subclassing the NSTableView. While I do move the inheritance up one notch on the chain from NSObject to NSResponder I'm not totally subclassing the NSTableView. I'm just adding the ability to override the keyDown event.
I made the class I was using as a delegate inherit from NSResponder instead of NSObject and set the nextResponder to that class in awakeFromNib. I was then able to trap key presses using the keydown event. I of course connected the IBOutlet and set the delegate in Interface Builder.
Here's my code with the minimum needed to show the trapping of the key:
Header file
// AppController.h
#import <Cocoa/Cocoa.h>
#interface AppController : NSResponder {
IBOutlet NSTableView *toDoListView;
NSMutableArray *toDoArray;
}
-(int)numberOfRowsInTableView:(NSTableView *)aTableView;
-(id)tableView:(NSTableView *)tableView
objectValueForTableColumn:(NSTableColumn *)aTableColumn
row:(int)rowIndex;
#end
Here's the m file.
// AppController.m
#import "AppController.h"
#implementation AppController
-(id)init
{
[super init];
toDoArray = [[NSMutableArray alloc] init];
return self;
}
-(void)dealloc
{
[toDoArray release];
toDoArray = nil;
[super dealloc];
}
-(void)awakeFromNib
{
[toDoListView setNextResponder:self];
}
-(int)numberOfRowsInTableView:(NSTableView *)aTableView
{
return [toDoArray count];
}
-(id)tableView:(NSTableView *)tableView
objectValueForTableColumn:(NSTableColumn *)aTableColumn
row:(int)rowIndex
{
NSString *value = [toDoArray objectAtIndex:rowIndex];
return value;
}
- (void)keyDown:(NSEvent *)theEvent
{
//NSLog(#"key pressed: %#", theEvent);
if (theEvent.keyCode == 51 || theEvent.keyCode == 117)
{
[toDoArray removeObjectAtIndex:[toDoListView selectedRow]];
[toDoListView reloadData];
}
}
#end