populating a tableview confusion - objective-c

Using cocoa on mac osx 10.7 I populated a TableView with some rows, I have some confusions,
names is an NSArray* containing NSString*
- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex;
{
return [names objectAtIndex:rowIndex];
}
I checked the protocol reference for the datasource in the apple's documentation
https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Protocols/NSTableDataSource_Protocol/Reference/Reference.html
I am returning an NSString* in the above function, the signature of the method only returns id and not NSString* . The documentation gave me no clue about the return type it just says
An item in the data source in the specified tableColumn of the view.
My case was simple NSString* made sense and it worked but I am not sure. I am wondering where is this implicit knowledge coming from as to what type of data has to be returned by this method given there is no mention in the signature and nothing in the method's documentation :/
Any advise ? I am coming from .Net/android/ background the documentations there made more sense probably because those are strongly typed lanuguages ,...

The type of object that you should return is based on how the cells in your NSTableView are defined. By default when you create an NSTableView in Interface Builder, the cells are defined as NSTextFieldCell, so can return an NSString for those cells. However you can define the cells to accept other types of objects in your code (for example NSImageCell, NSButtonCell, etc).

Related

Observe changes of instance variable in Objective-c

I have a UITableView with a property(strong, nonatomic)NSMutableArray *currentContent, I also have a property(strong, nonatomic)NSMutableArray *cellHeights to keep track of the cell heights cos the user could expand or collapse each cell. The self.currentContent is set by another controller which load data from a web service, so it will change as the data load, I want to keep both of these variables in sync. As soon as currentContent is updated, I want to update cellHeights. How do I do that?
I tried:
- (void)setCurrentContent:(NSMutableArray *)currentContent{
_currentContent = currentContent;
self.cellHeights = [NSMutableArray arrayWithDefaultHeightsForCellCount:[currentContent count]];
}
But it's not working, cos it will only be set at the first time when I set currentContent, when it's empty. So self.cellHeights currently will stay empty. When there is finally value in self.currentContent, self.cellHeights was not updated.
I've done a similar thing before, with variable cell heights depending on the content from your web-service, and I'd advise that keeping an array of cell heights might not be the best idea.
What I did was to create a 'fake' cell in the viewDidLoad: method, that I use just to calculate cell heights.
Then I use the 'heightForRowAtIndexPath' method to specify how tall cell should be by populating the 'fake' cell with the data for the index path, then finding out how tall that cell is. For example:
#interface MyTableViewController()
#property (nonatomic, strong ) MyCustomTableViewCell *cellForTestingHeight;
#end
#implementation MyTableViewController
- (void)viewDidLoad {
[super viewDidLoad]
self.cellForTestingHeight = [[MyCustomTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault];
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
NSDictionary *myData = [self.data objectAtIndex:indexPath.row];
self.cellForTestingHeight.viewData = myData;
return self.cellForTestingHeight.height;
}
#end
This code assumes that you've created a class called MyCustomTableViewCell which has a method to set the viewData property on it, and that after setting that property you'll be able to tell how tall that cell will be by accessing a height property.
What you need to do is observe changes to the mutable array itself, not your property which references the array. This should be straightforward but unfortunately there is a twist...
OK, let's assume there is no twist and rather than an NSMutableArray your property is of type MyObservableClass. Then to setup the observing you would do something like this (all code is pseudo - i.e. typed into the answer):
- (void)setCurrentContent:(MyObservableClass *)currentContent
{
if(_currentContent)
[_currentContent removeObserver:self forKeyPath:#"myObservableProperty"];
_currentContent = currentContent;
[_currentContent addObserver:self forKeyPath:#"myObservableProperty" ...];
}
Now whenever the currentContent is changed your code stops observing the properties of its old value and starts observing properties of its new value.
Easy, but its not quite that simple for arrays...
The twist, though KVO will inform an observer of additions, deletions and changes to a collection the NSMutableArray class doesn't issue those notifications. You need to use a proxy for the array, and such a proxy is provided by the frameworks - see for example NSArrayController and mutableArrayValueForKey:. However the second part of the twist is that the changes to the array must be done through this proxy - you can't have your client call setCurrentContent and pass any NSMutableArray if you are going to observe it, your client needs to pass in the proxy... Whether you can use this easily in your design you'll have to figure out. You'll need to read Automatic Change Notification in this document and other Apple docs to sort this out.
This sounds more complicated that it needs to be, maybe somebody else will provide a more succinct way of doing this.

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

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.

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

Using Array Controllers to restrict the view in one popup depending on the selection in another. Not core data based

I am working on an app that is not core data based - the data feed is a series of web services.
Two arrays are created from the data feed. The first holds season data, each array object being an NSDictionary. Two of the NSDictionary entries hold the data to be displayed in the popup ('seasonName') and an id ('seasonID') that acts as a pointer (in an external table) by matches defined for that season.
The second array is also a collection of NSDictionaries. Two of the entries hold the data to be displayed in the popup ('matchDescription') and the id ('matchSeasonId') that points to the seasonId defined in the NSDictionaries in first array.
I have two NSPopUps. I want the first to display the season names and the second to display the matches defined for that season, depending on the selection in the first.
I'm new at bindings, so excuse me if I've missed something obvious.
I've tried using ArrayControllers as follows:
SeasonsArrayController:
content bound to appDelegate seasonsPopUpArrayData.
seasonsPopup:
content bound to SeasonsArrayController.arrangedObjects; content value bound to SeasonsArrayController.arrangedObjects.seasonName
I see the season names fine.
I can obviously follow a similar route to see the matches, but I then see them all, instead of restricting the list to the matches for the season highlighted.
All the tutorials I can find seem to revolve around core data and utilise the relationships defined therein. I don't have that luxury here.
Any help very gratefully received.
This is not an answer - more an extension of the previous problem.
I created MatchesArrayController and subclassed it from NSArrayController to allow some customisation.
Following the example in 'Filtering Using a Custom Array Controller' from 'Cocoa Bindings Topics', I followed the same idea as above:
MatchessArrayController: content bound to appDelegate matchesPopUpArrayData.
matchesPopup: content bound to MatchesArrayController.arrangedObjects; content value bound to MatchesArrayController.arrangedObjects.matchDescription.
I've derived the selected item from seasonPopUp:sender and used this to identify the seasonId.
The idea is to change the arrangedObjects in MatchesArrayController by defining the following in;
- (NSArray *)arrangeObjects:(NSArray *)objects
{
if (searchString == nil) {
return [super arrangeObjects:objects];
}
NSMutableArray *filteredObjects = [NSMutableArray arrayWithCapacity:[objects count]];
NSEnumerator *objectsEnumerator = [objects objectEnumerator];
id item;
while (item = [objectsEnumerator nextObject]) {
if ([[[item valueForKeyPath:#"matchSeasonId"] stringValue] rangeOfString:searchString options:NSAnchoredSearch].location != NSNotFound) {
[filteredObjects addObject:item];
}
}
return [super arrangeObjects:filteredObjects];
}
- (void)searchWithString:(NSString *)theSearchString {
[self setSearchString:theSearchString];
[self rearrangeObjects];
}
- (void)setSearchString:(NSString *)aString
{
[aString retain];
[searchString release];
searchString=aString;
}
I've used NSLog to check that things are happening the way they are supposed to and all seems ok.
However, it still doesn't do what I want.
[self rearrangeObjects]; is supposed to invoke the arrangeObjects method but doesn't. I have to call it explicity
(i.e.[matchesArrayController arrangeObjects:matchesPopUpArrayData]; )
Even then, although filteredObjects gets changed the way it is supposed to, the drop down list does not get updated the way I want it to.