Getting checkboxes to reflect their state in an NSTableColumn - objective-c

I'm making this program that has an NSTableView with four columns, two of which are make of checkboxes. I'm only trying to get one working right now and I've gotten stuck.
First, here's my relevant code:
- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView {
NSString *filePathThree = [[NSBundle mainBundle] pathForResource:#"mydictionary" ofType:#"plist"];
NSData *myDataThree = [[NSData alloc]initWithContentsOfFile:filePathThree];
self.flozzCodeAndName = (NSMutableDictionary *)[NSPropertyListSerialization
propertyListFromData:myDataThree
mutabilityOption:NSPropertyListMutableContainersAndLeaves
format:NULL
errorDescription:NULL];
return [[flozzCodeAndName objectForKey:#"name"] count];
}
- (void)tableView:(NSTableView *)tableView
setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex
{
NSButtonCell *cell;
cell = [[NSButtonCell alloc] init];
[cell setButtonType:NSSwitchButton];
[cell setTitle:#""];
[cell setTag:rowIndex];
NSLog(#"%d", [cell tag]);
[cell setCellAttribute:NSCellEditable to:3];
[cell setImagePosition:NSImageOnly];
[cell setState:NSOnState];
NSLog(#"%d", [cell state]);
[havzColumn setDataCell:cell];
[myTableVeew reloadData];
[cell release];
}
- (id)tableView:(NSTableView *)aTableView
objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex {
NSString *filePathThree = [[NSBundle mainBundle] pathForResource:#"mydictionary" ofType:#"plist"];
NSData *myDataThree = [[NSData alloc]initWithContentsOfFile:filePathThree];
self.flozzCodeAndName = (NSMutableDictionary *)[NSPropertyListSerialization
propertyListFromData:myDataThree
mutabilityOption:NSPropertyListMutableContainersAndLeaves
format:NULL
errorDescription:NULL];
NSArray *myArray = [flozzCodeAndName objectForKey:[aTableColumn identifier]];
NSString *myStringValue = [myArray objectAtIndex:rowIndex];
return myStringValue;
}
As you can see, I'm using the data source method for this table rather than bindings. The book I read for Cocoa made some checkboxes with tags, but I think they were in an array, so that might not be the best thing to do.
Anyway, when I run this, the debugger will show me the tag (which equals the row) along with the state of the button (1 for all of them because of NSOnState). My problem is that I cannot get the boxes to check and uncheck depending on their state. I read this question: Checkbox on table column won't register click
And then the NSTableView datasource reference. According to Mr. Nozzi in the linked question, it seems to me that an array containing the states for the boxes is needed, so I tried that, setting [cell state] to an NSNumber to get it into an NSMutableArray. I FUBAR'd that and don't think that was right. There are 454 rows in this table (tags go to 453 because of arrays starting at 0), for all four columns.
I also wonder if I should put the cell definition stuff that is in tableview:setObjectValue: into an 'awakeFromNib'. I did put a checkbox button cell in the IB, but I was having problems with it earlier, so I decided to do it programmatically too. During all of these, I did, and still do, have a [myTableVeew reloadData] in the setObjectValue method.
The assistance is appreciated, if any other info is needed, I can get it.

You have two problems: Your data source keeps getting blown away and you're not using the ...objectValue... method properly.
Data Source:
You're blowing away your data source in your -numberOfRowsInTableView: method and replacing it every time the table needs to do a refresh. You'll want to cache (a mutable copy of) your dictionary only when you need to (like at application launch) then only refer to it from the table data source methods. Perhaps you should move it to an instance variable and use proper accessors.
Also, the documentation mentions that, because the data source methods are called very frequently, they should be fast, so from a performance viewpoint alone this is not a good idea. You should only do what it takes to answer the question the delegate method is posing to keep the table responsive with large data sets.
Object Value: You should ONLY be returning the object value from this method (usually an NSNumber object containing the state the checkbox is meant to toggle.
You should set your table column's -dataCell when the view is loaded or at application launch. Even easier: drag a check box cell into the table column in Interface Builder to set that as the data cell without code.
Additional Observation: If you plan to persist the changes to this information in any way, note that you must never rely on the application bundle being writable and should never attempt to overwrite resource files like the one you're loading from your bundle. You'll need to save the information elsewhere, using your bundle copy as a template copy only.

Related

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

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];
}

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

When does objectValueForTableColumn: get called?

I am currently going through the 3rd Edition of the Mac OSX Cocoa book from the Big Nerd Ranch guys and I am editing my program to include table views. Here the code from the AppController.m file where I have to implement the required protocol methods from the TableView:
-(id)init{
self = [super init];
voiceArray = [NSSpeechSynthesizer availableVoices];
speechSynth = [[NSSpeechSynthesizer alloc] initWithVoice:nil];
[speechSynth setDelegate:self];
return self;
}
-(int)numberOfRowsInTableView:(NSTableView *)tv{
NSLog(#"Getting number of rows in table view: %lu", [voiceArray count]);
return [voiceArray count];
}
-(id)tableView:(NSTableView *)tv objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row{
NSString *v = [voiceArray objectAtIndex:row];
NSDictionary *dict = [NSSpeechSynthesizer attributesForVoice:v];
NSLog(#"Voice Name %#", [dict objectForKey:NSVoiceName]);
return [dict objectForKey:NSVoiceName];
}
-(void)tableViewSelectionDidChange:(NSNotification *)notification{
int row = [tableView selectedRow];
NSLog(#"row selected %d", row);
if(row == 1){
return;
}
NSString *selectedVoice = [voiceArray objectAtIndex:row];
[speechSynth setVoice:selectedVoice];
NSLog(#"new voice %# ", selectedVoice);
}
When the app first loads I get the following output:
2012-05-27 15:02:29.040 Speakline[42836:f03] Getting number of rows in
table view: 24 2012-05-27 15:02:29.042 Speakline[42836:f03] row
selected 2 2012-05-27 15:02:29.043 Speakline[42836:f03] new voice
com.apple.speech.synthesis.voice.Alex 2012-05-27 15:02:29.162
Speakline[42836:f03] Voice Name Agnes 2012-05-27 15:02:29.163
Speakline[42836:f03] Voice Name Albert
I want to make sure I understand fully what is going on here. In order to do this I have a couple of questions.
It looks like the numberOfRowsInTableView: method was automatically called after the init method. Is this correct?
How often does objectValueForTableColumn: get called? What events prompt that method to get called? Also, in the code there, the return statement confused me. Where exactly does this return value go?
As a side note they wanted me to connect the Outlets and the AppController via control+clicking (via the connections panel) and linking them in that way. What alternatives are there avaialble for connecting delegates and datasources to different kinds of views without doing this? I am assuming that adding NSTableViewDelegate in the controller header file might be one way. If you have the option of control+click connecting all your views to outlets and so on vs programmatically setting it all up is it just a matter of preference at this point? It just seems to me that in order to understand what is going on it might be better to just write the code yourself.
1) Both numberOfRowsInTableView and objectValueForTableColumn:Row: get called soon after the nib is unarchived (I'm not sure of the exact order of things), so the table view can be populated with data.
2) The loading of the table and reloading or adding new data as well as scrolling will cause objectValueForTableColumn:row: to be called (by the table view). It gets called for each row that is populated. The value goes to the table view, that's how it gets its data.
3) The other way to connect delegates is to do it in code with setDelegate:, but putting NSTableViewDelegate in the .h file does not do that -- that's just a promise to the compiler that you will implement some or all of the delegate messages.

Can't get contents of file from a .plist file into an array

I'm trying to create a create a simple app where I have a TableView that should display the contents of a file. I have created a Table View in IB and dragged it's delegate and data source to the file's owner and I have manually created a .plist file with 1 array that have 2 items.
In my TableViewController.h i have declared an array.
NSArray * posts;
In my implementation file I have declared the required methods for UITableViewDataSource like this:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSLog(#"Returning num sections");
return posts.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// create a cell
UITableViewCell * post = [[UITableViewCell alloc]
initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"post"];
// fill it with content
post.textLabel.text = [posts objectAtIndex:indexPath.row];
// return it
return post;
}
And in my ViewController 'viewDidLoad' method I try to add the content of my file to the 'posts' array like this:
- (void)viewDidLoad
{
NSString * postFile = [[NSBundle mainBundle] pathForResource:#"Posts" ofType:#"plist"];
posts = [[NSArray alloc] initWithContentsOfFile:postFile];
NSLog(#"%#", postFile);
NSLog(#"%i", posts.count);
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
}
NSLog(#"%i", posts.count); returns 0, despite that I have added values to my .plist file. And nothing is displayed in the table view.
Suggestions on how so solve this would be appreciated.
I think you need to reload your table after you've loaded your postFile NSArray. If your view controller is a UITableViewController, try adding the following line of code to the end of your viewDidLoad method:
[self.tableView reloadData]
(On an unrelated note, you should also make your call to the super class the first thing you do in the viewDidLoad method, hence the comment the xcode template gives you.)
Edit: Problem with count.
I think you also have a problem with your debugging. count isn't a property of NSArray, so you can't use the dot syntax with it. You should be sending a message to your NSArray instance i.e. [posts count].
Ok, it seems like Xcode 4 creates plists with Dictionary as it's root type. If you want to use an array you have to open the .plist file in another text editor (probably doable in Xcode too) and change < dict >< /dict /> to < array >.
Also, it wasn't necessary to use an array at all. This also worked:
// Changed my array to a dictionary.
NSDictionary * posts;
// Get the cell text.
NSString * cellText = [[NSString alloc] initWithFormat:#"%i", indexPath.row];
// fill it with content
post.textLabel.text = [posts valueForKey:cellText];

Pointers and data assignment in Objective-C

I'm having trouble dealing with pointers in Objective-C. Basically, I have the following structure in my class :
UITableView *list;
NSArray *objArray;
UIPickerView *pickerCtrl;
My "list" shows the data contained in objArray, which is a temporary structure linking to custom NSObjects of various types (not stored in my current object).
Choosing one element in the list shows the "pickerCtrl", displaying appropriate data depending on which TableView line is currently selected.
My goal is to replace oldObject's data (the external object, accessed by objArray) with newObject's data (selected in the PickerView). Like this :
- (void)pickerView:(UIPickerView *)thePickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component {
id oldObject = [objArray objectAtIndex:[[list indexPathForSelectedRow] row]];
id newObject = [pickerData objectAtIndex:row];
*oldObject = *newObject;
}
From the debugger, oldObject and newObject both have the right memory addresses. The problem is, no assignation seems to be done, and the old data is never replaced by the data from "newObject".
What am I missing here ?
This is not the proper way to deal with mutable arrays, you are thinking too low-level.
Rather, try this:
[objArray removeObject:oldObject];
[objArray addObject:newObjec];
You can also use the insertObject:atIndex: method. See the reference for NSMutableArray for further information
Use:
- (void)exchangeObjectAtIndex:(NSUInteger)idx1 withObjectAtIndex:(NSUInteger)idx2
Example:
[objArray exchangeObjectAtIndex:[[list indexPathForSelectedRow] row],row];