(OS X) Programmatically bound NSTableView does not display values - objective-c

I created a viewController with a tableView and set the identifier of the only column to "name". Then I created an arrayController, bound it to a NSManagedObjectContext and set the right entity name.
When I now load the viewController, the tableView does display the correct amount of row. But unfortunately the cells do not contain the value of the NSManagedObjects value for the key name.
What do I have to implement in my NSManagedObject subclass or in the viewController (which is the tableViews viewController)?
I'd like to show you some code, but I don't know what could be helpful here, because it's more an conceptional question... So I'll post code as requested in comments.
UPDATE
This is the code I'm using to bind the arrayController tho the tableView:
[_tableView bind:NSContentBinding toObject:_arrayController withKeyPath:#"arrangedObjects.name" options:nil];
To inspect what the tableView gets, I added this line (after adding property called "content"):
[self bind:NSContentBinding toObject:_arrayController withKeyPath:#"arrangedObjects.name" options:nil];
In the setter I got an array containing NSString instances. But the tableView still does not display any values...

I finally used the standard NSTableViewDataSource protocol:
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
return [[_arrayController arrangedObjects] count];
}
- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
return [[[_arrayController arrangedObjects] objectAtIndex:row] valueForKey:[tableColumn identifier]];
}
I think this is a solid solution, though. I might became obsessed by the -bind:toObject:withKeyPath:options: idea.

Related

Custom NSView inside a column of a NSTableView

I am using a view-based NSTableView with only one column.
I have the following code, which returns an NSView with two NSTextFields:
- (id) tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
NSView *v = [[NSView alloc] init];
NSTextField *t1 = [[NSTextField alloc] init];
[t1 setStringValue:#"test1"];
NSTextField *t2 = [[NSTextField alloc] init];
[t1 setStringValue:#"test2"];
[v addSubview:t1];
[v addSubview:t2];
return v;
}
It seems that nothing happens, even if this code gets executed. I am not using the column identifier, I just want to return a custom view as a row.
This is the result:
What am I doing wrong?
This view will be very custom and large, so I cannot rely on Apple's "default" cell views.
Thank you!
View-based is a different than cell-based.
All that is different is replacing the "id" return value to "NSView".
Change this:
- (id)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item
To this:
- (NSView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item
This is given the assumption you are running 10.7, and you are actually going to return an NSView.
A few things to check:
Does your NSTableView have its delegate set to the class that
implements the NSTableViewDelegate protocol? (I ask because
tableView:viewForTableColumn:row: is a delegate method whereas the
tableView:objectValueForTableColumn:row: is a data source method.)
Are there any bindings that are set in IB? Check mainly for bindings
to the NSTableView content binding and to the provided view's
subviews (usually the text field.)
Do you implement the numberOfRowsInTableView: method from the
NSTableViewDataSource protocol and does it return a nonzero integer?
Finally, you might have a look at the Table View Programming Guide chapter on "Populating View-Based Table Views Programmatically" in the documentation.

*** Illegal NSTableView data source (<NSView: 0x102535290>). Must implement numberOfRowsInTableView: and tableView:objectValueForTableColumn:row:

OK, this is driving me crazy, i have an Xcode OSX App that i have been working on. I made some changes recently and i have started getting the following error at compile time:
iModerate Desktop[72478:303] *** Illegal NSTableView data source (<NSView: 0x102535290>).
Must implement numberOfRowsInTableView: and tableView:objectValueForTableColumn:row:
I cannot workout where this is coming from, i have implemented both these methods in my appDelegate:
- (NSView *)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row{
// The return value is typed as (id) because it will return a string in most cases.
id returnValue=nil;
// The column identifier string is the easiest way to identify a table column.
NSString *columnIdentifer = [tableColumn identifier];
// Get the name at the specified row in namesArray
NSString *theName = [[self.twitterClientsController arrangedObjects] objectAtIndex:row];
// Compare each column identifier and set the return value to
// the Person field value appropriate for the column.
if ([columnIdentifer isEqualToString:#"name"]) {
returnValue = theName;
}
return returnValue;
}
and this
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
return[[self.twitterClientsController arrangedObjects] count];
}
And app delegate is set as NSTableViewDelegate
Now the extra weird/frustrating thing, is that i have no NSTableView in the xib, i did, but i have deleted them all. I have event opened the XIB in BBedit and searched for NSTableView and there is 100% not one in there!
So, help please! If i could work out what NSView: 0x102535290 is i could maybe track this down.
Help to save my sanity greatly appreciated!
Gareth
it is solved with me that way :
remove the datasource and the delegate connections from the interfaceBuilder.
make an outlet property for the your tableview in the .h file
in the .m file in applicationDidFinishLaunching method set the delegate and datasource manually for your tableview
[self.tableView setDelegate:self];
[self.tableView setDataSource:self];

Custom NSView for NSTableView not updating components

I created a custom NSView for a NSTableView.
I am trying to fill the fields I created in it using the Interface Builder but I can't. I would say each component is properly linked and the code is OK.
This is the function
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
Account *account = (Account *)[self.dataSource objectAtIndex:row];
AccountTableCellViewController *controller = [[AccountTableCellViewController alloc] init];
controller.subtitleLabel.stringValue = account.name;
[controller.titleLabel setStringValue:account.num];
NSLog(#"%#", controller.titleLabel);
return [controller view];
}
And here is the picture of the bindings:
The table shows all the rows correctly, but is not filling the NSTextFields as expected.
UPDATE:
This is how it looks like (not updating views):
Any suggestion?
The labels haven't been loaded from the nib yet. I'd just create an extra account instance variable in your view controller, assign it there, then load the information in -(void)awakeFromNib

View-based NSTableView yields odd results with bindings and delegate

I have an NSTableView, the content of which is bound to an arrayController using a filterPredicate. The tableView is view-based, so its delegate is set to an object that has the
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
method. I'm getting odd behavior, which may be related to my observation that this method is being called even when the controller's number of arrangedObjects is 0. Moreover, each time the method is called, the number of rows it asks for is the total number of objects in the array controller's content (as opposed to arrangedObjects). It think it may be because of this that when I set the objectValue of the cellView requested using
cellView.objectValue = [arrayController arrangedObjects] objectAtIndex:row];
that it is often the wrong one. Any ideas?
objectValueForTableColumn is not necessary for the case of bindings. Just banged my head against a wall for a few minutes and came back with this path of least resistance:
1) bind your NSOutlineView content to the NSTreeController (arrangedObjects controller key)
2) bind the subviews of each of the NSTableCellView to the enclosing NSTableCellView (objectValue.foo where foo is the key path for your target field)
3) set the identifier for each column in your outline view (I do this for tracking reordering of table columns between launches)
4) make sure each NSTableCellView has the identifier set to Automatic (or is the same as the table column) ** This is important and messed with me **
5) when you call makeViewWithIdentifier: make sure you pass tableColumn.identifier
This assumes you only have one cell view type per table column. Otherwise, you'll need to pass in the correct identifier for the cell view you want in makeViewWithIdentifier:.
Well, I still can't explain why the view was behaving as it was, but I did manage to get things to behave correctly, doing the following:
binding tableView content to arrayController's arrangedObjects
binding various elements of the tableCellview used in the tableView to the appropriate keyPath of the tableCellView's objectValue
providing each tableCellView as follows (where BILSelectableRoundedTableCellView happens to be a subclass of NSTableCellView):
(NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
NSInteger numberOfObjects = [self.arrayController.arrangedObjects count];
if (numberOfObjects == 0)
return nil;
if (row >= numberOfObjects)
return nil;
BILSelectableRoundedTableCellView *viewForTableColumnRow = [self.myTableView makeViewWithIdentifier:#"DataCell" owner:self];
return viewForTableColumnRow; }
Finally, using a datasource that implements the following:
(id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
NSInteger numberOfObjects = [self.arrayController.arrangedObjects count];
if (numberOfObjects == 0)
return nil;
if (row >= numberOfObjects)
return nil;
return [self.arrayController.arrangedObjects objectAtIndex:row]; }
The use of the later seems contrary to the documentation out there, but is necessary to get things to work properly. Interestingly, the other datasource method (numberOfRowsInTableView) is not called when using bindings, although it is called when i'm not using bindings.

Delegate events for NSTextField in a view-based NSOutlineView?

I have a flawless functioning view-based NSOutlineView with a proper set-up datasource in my project. Now I want to allow the user to change certain entries. So I made the NSTextField in the IB editable. For a cell-based NSOutlineView you can use the delegate method outlineView:setObjectValue:forTableColumn:byItem: however it's not available for a view-based NSOutlineView as stated in the header file for the NSOutlineViewData protocol:
/* View Based OutlineView: This method is not applicable.
*/
(void)outlineView:(NSOutlineView *)outlineView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn byItem:(id)item;
So I searched for another delegate method and found outlineView:shouldEditTableColumn:item:. However this delegate method doesn't get fired. Probably because I'm not editing a cell.
So my question is: Is there any other way to notice when a row changed than having a delegate for each NSTextField?
You are correct that your text field needs to be editable in Interface Builder.
Next, make your controller conform to NSTextFieldDelegate. Then, set the delegate for the text field in outlineView:viewForTableColumn:item:, like so:
tableCellView.textField.delegate = self
Here's a simplified example, where you've implemented the method for returning the table cell view for an item for your outline view.
-(NSView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item
{
NSTableCellView *tableCellView = [outlineView makeViewWithIdentifier:#"myTableCellView" owner:self];
MyItem *myItem = (MyItem *)item; // MyItem is just a pretend custom model object
tableCellView.delegate = self;
tableCellView.textField.stringValue = [myItem title];
tableCellView.textField.delegate = self;
return result;
}
Then, the controller should get a controlTextDidEndEditing notification:
- (void)controlTextDidEndEditing:(NSNotification *)obj
{
NSTextField *textField = [obj object];
NSString *newTitle = [textField stringValue];
NSUInteger row = [self.sidebarOutlineView rowForView:textField];
MyItem *myItem = [self.sidebarOutlineView itemAtRow:row];
myItem.name = newTitle;
}
Well, it seems like Apple wants us to use the delegate methods of each NSTextField as stated here:
This method is intended for use with cell-based table views, it must not be used with view-based table views. Instead target/action is used for each item in the view cell.
So there's currently no other way to do this.