I have a two column NSTableView that I want to populate with entries grabbed from a CSV file. Everything works fine except the title of each of the table cells isn't set.
Here's the function that is called for each row in the NSArray
- (void)populateTable:(NSString *)fname :(NSString *)lname {
NSMutableDictionary *value = [[NSMutableDictionary alloc] init];
[value setObject:[NSString stringWithFormat:#"%#", fname] forKey:#"first_name"];
[value setObject:[NSString stringWithFormat:#"%#", lname] forKey:#"last_name"];
[arrayController addObject:value];
[value release];
[fname_list reloadData];
[lname_list reloadData];
}
The value of the "last name" column is bound to the Array Controller arrangedObjects.last_name and both "first_name" and "last_name" are listed as keys in the Array Controller > Object Controller > Keys list
The proper amount of table cells are created (3 entries in the CSV file = 3 rows in each column), but the title of each of the cells isn't set (either to "first_name" or "last_name" depending on the column); the title of the cells just remains "Table View Cell"
Edit:
After using navinsillu's changes I end up with the following function:
- (void)populateTable:(id)sender :(NSString *)fname :(NSString *)lname {
[arrayController insert:sender];
[[[self arrayController] selection] setValue:fname forKeyPath:#"first_name"];
[[[self arrayController] selection] setValue:lname forKeyPath:#"last_name"];
[fname_list reloadData];
[lname_list reloadData];
}
And in my header I have
#property (retain,nonatomic,readwrite) IBOutlet NSArrayController *arrayController;
But I still get the same results: 3 rows/cells in each table column, all titled "Table View Cell"
Edit:
After some inspection I noticed that the Model Key Path under the Value bind for the table columns doesn't have any entries. When I type in first_name or last_name it says No completions found even though the keys are listed in the Keys list in the array controller. Unfortunately I have no idea as to why it isn't finding the keys.
Edit:
Odd. I tried deleting and adding the table view and array controllers but that didn't work. However, creating a new project and doing the exact same things produces the expected results.
You can set your title at the table header in Interface Builder.you can double click the table column header and set title for it.
Recreating the project exactly the same way as I had it produces what I was expecting to happen. I'm not sure why redoing it all worked, but it did.
Related
In my app I have two NSOutlineView tables. My represented objects have dynamic attributes, so I have to add columns dynamically.
This is what the objects look like:
Instance properties
I use Core Data and Cocoa bindings to display and edit the values.
I can display all the attributes in the outline view, and (seemingly) edit them, too, but only certain properties trigger an NSManagedObjectContextObjectsDidChangeNotification. That means, that the data is not saved for certain columns -- unless I modify a value in a correctly working column, too. In that case, all data is subsequently saved, even the ones that did not trigger the context change.
This is a correctly working column:
NSTableColumn *nameColumn = [[NSTableColumn alloc] initWithIdentifier:#"Name"];
nameColumn.title = #"Name";
nameColumn.width = 200;
[self.instancesOutlineView addTableColumn:nameColumn];
[nameColumn bind:NSValueBinding
toObject:self.instancesTreeController
withKeyPath:#"arrangedObjects.name"
options:nil];
However, the columns which are created from the NSDictionaryproperty do not trigger the context change:
NSTreeNode *selectedNode = [self.typesOutlineView itemAtRow:[self.typesOutlineView selectedRow]];
InstanceType *it = (InstanceType *)[selectedNode representedObject];
// Get Instance Dynamic Attributes dictionary
Instance *i = it.instances.anyObject;
NSDictionary *instanceDynAtts = i.dynamicAttributes;
for (NSString *key in instanceDynAtts) {
NSTableColumn *column = [[NSTableColumn alloc] initWithIdentifier:key];
column.title = key;
[self.instancesOutlineView addTableColumn:column];
NSString *keyPath = [NSString stringWithFormat:#"arrangedObjects.dynamicAttributes.%#.value", key];
[column bind:NSValueBinding
toObject:self.instancesTreeController
withKeyPath:keyPath
options:nil];
}
What could be the problem here? Is there a way to force saving the context, even if it hasn't changed?
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
I have a text field in my app. I am trying to store whatever is entered into the text field in an array and display it in my root view controller (which is a table view) on click of a button.
The method for the button is as follows:
-(IBAction)addNewCountry:(id)sender
{
[rootViewController.details addObject:nameField.text];
NSLog(#"Country name is %#", rootViewController.details);
[self.navigationController pushViewController:rootViewController animated:YES];
[rootViewController.tableView reloadData];
NSLog(#"new country added");
}
details is the array declared in RootViewController
However, the text field text is not retrieved. Can anybody tell me what am i missing?
Change this
[rootViewController.details addObject:nameField.text];
to this
NSString *name = [NSString stringWithString: nameField.text];
[rootViewController.details addObject: name];
You cont directly insert your string data to your array, you need to store as a string
NSString *stringval = [NSString stringWithString: nameField.text];
then add it in to your string using addobject
[rootViewController.details addObject: stringcal];
Is your details array declared as NSMutableArray? It must be declared as an NSMutableArray in order for it to be modifiable. Also when you initialized the array did you add this after the allocation:
[details retain];
I've noticed that in order to do a search of a table, a copy of that data must be inserted to a search array.
E.g.
//Initialize the array.
listOfItems = [[NSMutableArray alloc] init];
NSArray *countriesToLiveInArray = [NSArray arrayWithObjects:#"Iceland", #"Greenland", #"Switzerland", #"Norway", #"New Zealand", #"Greece", #"Rome", #"Ireland", nil];
NSDictionary *countriesToLiveInDict = [NSDictionary dictionaryWithObject:countriesToLiveInArray forKey:#"Countries"];
NSArray *countriesLivedInArray = [NSArray arrayWithObjects:#"India", #"U.S.A", nil];
NSDictionary *countriesLivedInDict = [NSDictionary dictionaryWithObject:countriesLivedInArray forKey:#"Countries"];
[listOfItems addObject:countriesToLiveInDict];
[listOfItems addObject:countriesLivedInDict];
//Initialize the copy array.
copyListOfItems = [[NSMutableArray alloc] init];
So what is searched is the objects that are stored in the copied array.
My Question is, how do I search Cell rows with text, subtext and image in that particular cell.
(1)
There isn't really any such thing as searching a table. What happens when the user enters text in a UISearchBar is totally up to you - you can make that operation mean anything you like. All you have to do is function as the delegate-and-data-source for the results table and form the results table in response to the standard Three Big Questions that form the basis for any table ("how many sections have you? how many rows in this section? what's the cell for this row?") in any way you like. The results table does often look like a reduced version of the original table, but this is not at all required! It can be any table you want it to be.
(2)
Don't confuse Model with View. The table is just a view. Your data is Model. It is the Model, your data that is the basis of the original table, that you are going to be searching. So when the user types in your UISearchBar and you start searching, you want to form a new Model that will be the basis of the results table. How you form it is completely up to you. Typically you'll want to filter the original model so that the only stuff left in your results model is stuff that counts as a valid result. You could do this by walking the whole original model, putting everything that matches the search criterial into the new model. Or, if the original model is an array, you could use one of the filteredArray methods to help you. The most flexible way is to form a predicate with a block, as in this example from my book:
NSPredicate* p = [NSPredicate predicateWithBlock:
^BOOL(id obj, NSDictionary *d) {
NSString* s = obj;
NSStringCompareOptions options = NSCaseInsensitiveSearch;
return ([s rangeOfString:sbc.searchBar.text
options:options].location != NSNotFound);
}];
self.filteredStates = [states filteredArrayUsingPredicate:p];
In that example, s (one item of the array) is a string each time, and I'm looking to see whether the user's search term occurs in that string. But if you had a dictionary or other structure holding both a title and a subtitle and info about an image, you could examine that dictionary in any way you like. It's just a matter of returning YES or NO according to whether this array item passes the test based on the search term, on whatever definition you attach to the notion of passing the test.
(3)
The big question remaining is when to form the results model. I usually start by making the results model identical to the original model in response to searchDisplayControllerWillBeginSearch, because otherwise the results table will say No Results while the user is typing. (That is probably why you think the first thing to do is copy the original model.) Then, I can either do the actual filtering in response to searchBarSearchButtonClicked (the user is done typing and has tapped Search), or if the model is small enough, I can filter it afresh after every letter the user types, in response to searchBar:textDidChange (the user has typed a letter in the search bar).
There are a few steps involved. Note that the code below is just an example that I'm typing in by hand now, so it probably won't compile, it's just to give you an idea.
1) Ensure that you have an array containing all the cell values.
2) Create a copy of that array, and use that copy as the data source when returning cells in your table delegate methods.
3) Set yourself up as delegate for the UISearchBar, and respond to its events:
- (void)searchBarButtonClicked(UISearchBar *)searchBar {
[self doSearch:searchBar.text];
}
- (void)searchBar(UISearchBar *)searchBar textDidChange:(NSString *)searchTerm {
if (searchTerm.length == 0) {
[self resetSearch];
[table reloadData];
}
else
[self doSearch:searchTerm];
}
- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar {
searchBar.text = #"";
[self resetSearch];
[table reloadData];
[searchBar resignFirstResponder];
}
4) Create the other methods
The resetSearch method just needs to copy your full data array to the data source array used by your table delegates:
- (void)resetSearch {
self.tableSourceArray = [self.dataSourceArray copy]; // Or write a deep copy if you want to.
}
Whereas when searching, we need to filter the datasource array. You may be able to create something more efficient - this is just an example.
- (void)doSearch:(NSString *)searchTerm {
NSMutableArray *filtered = [[NSMutableArray alloc] init];
for (NSString *item in self.self.dataSourceArray) {
if ([item rangeOfString:searchTerm options:NSCaseInsensitiveSearch].location != NSNotFound])
[filtered addObject:[item copy]];
}
self.tableSourceArray = filtered;
}
And that should be it!
Tim
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.