Adding object to a column in a cell-based NSTableView? - objective-c

I'm having trouble with Columns in NSTableview. I want to add an item to a specific column in an instance of NSTableView. (Note that data is a pointer to an instance of NSMutableArray, which is a property of AppDelegate, which is the dataSource)
-(IBAction)addTask:(id)sender
{
[self.data addObject:#""];
[self tableView:_tableView setObjectValue:#"Hello World" forTableColumn: self.column row:0];
[self.tableView reloadData];
}
A tutorial I went through showed me how to edit a single-column NSTableView. However, using the same implementation of the NSTableViewDataSource methods causes tableView to populate both columns. The pertinent implementation from the tutorial:
-(void)tableView:(NSTableView *)tableView
setObjectValue:(id)object
forTableColumn:(NSTableColumn *)tableColumn
row:(NSInteger)row
{
[self.data replaceObjectAtIndex:row withObject:object];
}
I reason part of the issue is that the above method doesn't do anything with the tableColumn parameter, but I'm still learning and have no idea how to proceed. (It's especially hard to find help because many modern tutorials involve view-based NSTableViews, and well I know that is best practice, I don't want to run away from this.) I hope my explanation was clear enough, and any help would be much appreciated.

The reason you are seeing the data populated in multiple columns is you are not differentiating what values go in what columns.
You should think of each item in your array as a row in your table view. If you wish to display different values in columns of that row you need a way to store those different values in the data source object. This can be done by using a concrete model object class that has multiple properties defined or by using a NSDictionary with different keys with their corresponding values.
For adding a row:
-(IBAction)addTask:(id)sender
{
NSMutableDictionary *newRow = [NSMutableDictionary dictionary];
[newRow setObject:#"Hello World" forKey:#"salutation"];
[self.data addObject:newRow];
[self.tableView reloadData];
}
So for the display code:
- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
{
NSDictionary *row = [self.data objectAtIndex:rowIndex];
NSString *columnIdentifier = [aTableColumn identifier];
return [row objectForKey:columnIdentifier];
}
You'll notice we are using the column identifier here. Normally you set this value in Interface Builder on the table column. In the case above the identifier needs to be the same as the key in the dictionary for which you wish to display information.
Finally allowing the user to set a value of a column in the table view:
-(void)tableView:(NSTableView *)aTableView setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger) rowIndex
{
NSDictionary *row = [self.data objectAtIndex:rowIndex];
NSString *columnIdentifier = [aTableColumn identifier];
[row setValue:anObject forKey:columnIdentifier];
}

Related

Populate NSTableView with unknown number of columns

I have a NSTableview and a button. NSTableview has a unknown number of columns.
The first column has a image well and 2 text boxes, the others (again, unknown number) are normal textbox columns.
The button opens up a file open dialogue. Once I choose the files (.jpg) I would like to process them.
So far everything is made (chose files, columns, etc..) what is missing is the populating of the table:
I have the loop that goes through all the selected files. What is the best way to do this:
display the image in the image well of the first cell,
type the filename in the first textbox of the first cell,
type the filepath in the second cell of the textbox,
type "YES" in all other columns.
My difficulty is that I have no idea how many columns will be there since it depends from the user. The number of columns will not change during Runtime. they are set up at startup based on the configuration. if the configuration is changed then the app should be reloaded.
I am a beginner in Objective-C/Cocoa programming.
EDIT:
additional info as requested:
It is a view based NSTableView
each column represents an action that has to be taken in a later moment on an image. the program user can decide what and how many actions to take, thats the reason for a unknown amount of columns in the table view.
You can add columns programmatically using addTableColumn:. This method takes an NSTableColumn instance that you can create in your code.
The rest of your architecture (displaying images, etc.) does not particularly change from "normal" code just because the columns have been added dynamically.
Here is a snippet that should get you started:
NSTableColumn* tc = [[NSTableColumn alloc] init];
NSString *columnIdentifier = #"NewCol"; // Make a distinct one for each column
NSString *columnHeader = #"New Column"; // Or whatever you want to show the user
[[tc headerCell ] setStringValue: columnHeader];
tc.identifier = columnIdentifier;
// You may need this one, too, to get it to show.
self.dataTableview.headerView.needsDisplay = YES;
When populating the table, and assuming that the model is an array (in self.model) of NSDictionary objects, it could go something like this;
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
NSString *columnIdentifier = tableColumn.identifier;
NSDictionary *rowDict = [self.model objectAtIndex: row];
NSString *value = [rowDict valueForKey: columnIdentifier]; // Presuming the value is stored as a string
// Show the value in the view
}
More in the docs.
When user adds a column or row, you should reflect it in your model (by binding or by code), so you know the size of your table, when you need to populating it.
set tableView.delegate (in code or in Interface Builder), reference here
implement:
- (NSView*) tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn*)tableColumn row:(NSInteger)row
{
Item* itemView = [tableView makeViewWithIdentifier:#"rowItem" owner:self];
/*Here you populate your cell view*/
id entryObject = [self.entries objectAtIndex:row];
[itemView setEntry:entryObject];
return itemView;
}
and then invoke [tableView reloadData];
maybe for you better to use this method
- (void)tableView:(NSTableView *)aTableView setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
Just see the NSTableViewDataSource and NSTableViewDelegate

Retrieving Data in A NSTableView

I have a three column Table View populated by two NSMutableDictionaries which share the same keys (ie key | value1 | value2 ) with dict1(key,value1) and dict2(key,value2).
I want to manually enter data in the third column and create the key/value objects in dict2. But when I do that, my code picks the wrong key :S
Here's the code for the delegate :
- (void)tableView:(NSTableView *)aTableView setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
{
if ([[aTableColumn identifier] isEqualToString:#"value2"])
{
[dict2 setValue:anObject forKey:[[[aTableView tableColumnWithIdentifier:#"key"] dataCellForRow:rowIndex] stringValue ]];
}
}
Any idea ?
EDIT : I want to complete my question : how can you retrieve the data displayed in a cell ?
I see no other way to use NSMutableDictionnaries with TableViews, because [dict allKeys] does not give the keys in the same order that the one that is used to display it ! And stocking a version of [dict allKeys] just seems redundant and stupid.
I think this delegate function you're using is used for showing the data in the tableview with the format data source. It's used for setting, not retrieving.
You may use - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath to set the data source for tableview.

Populating NSTableView from multiple NSArray records in one go

If I have a one to many relationship, how would I go about populating the NSTableView with such data that is found in an NSArray?
Say I have a data item called "Alphabet", and I want that when a user selects it out of a list, the return value(s) in :
-(id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
are the letters of the alphabet in different rows, one underneath the other.
Can that be done?
Thanks!
You need to either assign an object as the table view's datasource and have that object conform to the NSTableViewDataSource protocol, or you need to use an NSArrayController and Cocoa bindings.
If you use a datasource, implement the numberOfItemsInTableView: method and return the count of your array. You must also implement -tableView:objectValueForTableColumn:row::
-(id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
{
if(rowIndex > -1)
{
return [yourArray objectAtIndex:rowIndex];
}
return nil;
}

Obtain a stringValue from NSTableView

I have a simple NSTableView which I have loaded with data from a NSMutableArray. When I select a row (entry) in the tableView and modify it, I can find the row index, what I cannot get is the edited entry out as a string so I can modify the array. I can find lots of information on selecting rows, etc., but not on how to get the actual modified string. I keep thinking this should be real simple. Help please.
Part of my code:
- (IBAction)toDoEdit:(id)sender // Accept the edited data
{
NSString *toDoItem = [[toDoTableCell:toDoTableView dataCellFoTableColumn:0 row:rowToBeEdited] stringValue];
// I get the error "dataCellForTableColumn' method cannot be found.
[toDoArray replaceObjectAtIndex:rowToBeDeleted withObject:toDoItem];
[toDoTableView reloadData];
[toDoTableView deselectRow:rowToBeDeleted];
}
~~~~~~~~~~~
// This method should return the cell value of the selected row
- toDoTableCell:(NSTableView *)tableView dataCellForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
return toDoTableCell; // No errors here but toDoTableCell is nil.
}
The 'Add' data to tableView works, 'Delete' data from tableView works, I just cannot get the edited data out of tableView so I can reload the data with the corrections.
What you are looking for is an NSTableView Delegate method:
- (NSCell *)tableView:(NSTableView *)tableView dataCellForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
This will return the NSCell that is in the row and column that you specify. From the NSCell you should be able to extract the value that you need. Depending on how you are using your NSCell you would either call [cell stringValue] or [cell objectValue].
Try this:
– tableView:setObjectValue:forTableColumn:row:
in - NSTableViewDataSource Protocol Reference
--- Edited based on comment ---
Above method is called whenever user tries to edit a table row, it also provides user with changed value as parameter. If you are trying to edit the row in table itself then it should serve your purpose. You can simply check the objectValue obtained as parameter and verify if it is correct or not. In case it is incorrect you can modify the obtained value and set it in todoArray.
Briefly:
- (void)tableView:(NSTableView *)aTableView setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
{
// below case is an example, you can add your own
if([anObject isEqualToString:#"Incorrect"])
{
anObject = #"Correct";
}
// Considering todoArray is array of dictionary items containing keys as table-column identifiers
NSMutableDictionary *originalData = [todoArray objectAtIndex:rowIndex];
[originalData setValue:anObject forKey:[aTableColumn identifier]];
[toDoTableView reloadData];
}
To get the value being edited you can simply use this code in above method, before setting the new value:
NSString *editedValue = [[todoArray objectAtIndex:rowIndex] valueForKey:[aTableColumn identifier]];
Hope this helps :)
It is simple. Read up on Cocoa Bindings and NSArrayController.
Take a look at the NSTableView methods selectedColumn, selectedColumnIndexes, selectedRow and selectedRowIndexes. I guess they should provide you with the needed information.
Now you can query the model, i.e. the array, for the data you need.

Objective-C multiple method warning

I'm getting a warning in the following method:
- (void)tableView:(NSTableView *)aTableView
setObjectValue:(id)anObject
forTableColumn:(NSTableColumn *)aTableColumn
row:(int)rowIndex
{
NSString *identifier = [aTableColumn identifier];
Person *person = [employees objectAtIndex:rowIndex];
NSUndoManager *undo = [self undoManager];
[[undo prepareWithInvocationTarget:self] tableView:aTableView setObjectValue:[person valueForKey:identifier] forTableColumn:aTableColumn row:rowIndex];
if(![undo isUndoing])
{
[undo setActionName:#"Edit Person"];
}
[person setValue:anObject forKey:identifier];
[tableView reloadData];
}
I'm trying to implement undo so I figure I would call the same method and just pass the old value. The error I'm getting is "warning: multiple methods named '-tableView:setObjectValue:forTableColumn:row:' found". Any idea why?
The method on NSTableViewDataSource is declared as:
- (void)tableView:(NSTableView *)aTableView setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
Your method uses int as the type of the rowIndex variable. Change that to NSInteger and all will be fine.
The underlying issue is that Objective-C does not support co-variance or contra-variance in method argumentation, leading to the warning.
Note also that you are colluding data model with view layer. Undo in this fashion is going to be very very tricky; if you don't also manage the undo stack in the context of every sort of the table and/or addition/removal of rows, undo is going to apply the value to the wrong row.
You would be far better off reworking your app such that you have a proper separation of model view and controller.
Well, here's what got rid of the warning:
[((MyDocument *)[undo prepareWithInvocationTarget:self]) tableView:aTableView setObjectValue:[person valueForKey:identifier] forTableColumn:aTableColumn row:rowIndex];
Casting to the class that I was in cleared up the ambiguity I suppose.