Coloring NSTableView Text per row - objective-c

I have a NSTableView that is displaying an array of objects I have. For each of these objects (rows) I would like to change the color of the text displayed depending on the results of a function I run on each object;
So for example all the object in the table that exist in another list (or some other requirement) I want to display them in green text, and objects that don't exist display in red.
How would I go about doing this?

Assuming that you have NSTextFieldCell in your table (for other cells, setting text color may vary), you can achieve this by implementing a NSTableView's delegate method.
First, you have to define a delegate for the NSTableView, either in Interface Builder or in your code. This can be your application controller for example.
Then, just implement the following method:
- (void)tableView:(NSTableView *)aTableView willDisplayCell:(id)aCell forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex {
NSTextFieldCell *cell = aCell;
if (...) {
[cell setTextColor:[NSColor greenColor]];
} else if (...) {
[cell setTextColor:[NSColor redColor]];
} else {
[cell setTextColor:[NSColor blackColor]];
}
}
Each time the NSTableView will draw a cell, you have the opportunity to modify it before it get drawn.
Check out the NSTableViewDelegate documentation page for more details.

Related

Changing view for selected in view-based NSTableView

How do I change the view for the selected row when using a view-based NSTableView? Specifically, I'd like to have a simple NSView subclass for unselected rows and a more complex NSView subclass for the selected row which allows editing of more information associated with the row item.
An example is the way Things allows you to expand the item being edited as seen here: http://culturedcode.com/things/
My guess is that you want to use a different NSTableCellView subclass when the row is selected. I think you should be able to do something like this:
- (void)tableViewSelectionDidChange:(NSNotification *)notification
{
NSTableView *table = [notification object];
NSIndexSet *allColumns = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [[table tableColumns] count])];
[table reloadDataForRowIndexes:[table selectedRowIndexes] columnIndexes:allColumns];
}
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
if ([[tableView selectedRowIndexes] containsIndex:row])
{
// If the row is selected, return an instance of the class for selected views
SelectedTableCellView *selectedView = ...; // Get from makeViewWithIdentifier:
// Set up selectedView
return selectedView;
}
else
{
NonSelectedTableCellView *nonSelectedView = ...; // Get from makeViewWithIdentifier:
// Set up nonSelectedView
return nonSelectedView;
}
}
It might be nice if you elaborated a little bit more on what you mean by "change the view to a more complex view"
Nonetheless, you could for instance, implement - (void)tableViewSelectionDidChange:(NSNotification *)notification in the delegate of the table view, get the selected NSTableRowView if it is visible, and change it in what way you want, which includes making it more complex, expanding it (see below), etc.
To modify the size of a row, you would need to implement - (CGFloat)tableView:(NSTableView *)tableView heightOfRow:(NSInteger)row in the same delegate, and call the table view's -noteHeightOfRowsWithIndexesChanged to update the height for particular rows.
I think the app is created by NSOutlineView in outlineview only you can easily expand your selected row...
- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
{
if ([item isKindOfClass:[NSDictionary class]])
{
return YES;
}else
{
return NO;
}
}
I think this way is write..

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.

Understanding how different methods in OutlineView relate

I'm having trouble fully understanding all the different places you can return cells when using an OutlineView. As far as I can tell there are four places:
NSOutlineViewDataSource has:
outlineView:child
outlineView:objectValueForDataColumn
And NSOutlineViewDelegate has:
outlineView:willDisplayCell
outlineView:dataCellForTableColumn
If I have a outline view with different items, like the SourceList example, where do I do what and why? I have GroupItem headers and a tree of IconAndImage cells that subclass NSTextFieldCell. Where should these be instantiated and where should I set the styling, image and title?
What Cocoa means by the word cell is not the same as what you would call a cell in for example Excel.
In Cocoa, a cell is a NSCell subclass and could be considered as a light-weight reusable NSView. It is used to draw many items in the same way. E.g.
- (void)drawRect:(NSRect)draw_rect {
// ...
for ( id value in myDataArray ) {
[cell setObjectValue:value];
NSRect cellFrame = ...;
[cell drawWithFrame:cellFrame inView:self];
}
So a data source does not return cells, but instead return objects that are parameters to [(NSCell) -(void)setObjectValue:(id)value]. The delegate returns which cell-object to use for each item and should be implemented so that you only create each cell-type once. E.g.
- (NSCell *)outlineView:(NSOutlineView *)outlineView
dataCellForTableColumn:(NSTableColumn *)tableColumn
item:(id)item {
NSCell *cell = nil;
switch(tableColumn.tag) {
case 0:
if ( ! myCell ) {
myCell = [[NSCell alloc] init];
}
cell = myCell;
break;
default:
break;
}
return cell;
}
You should use table column tags or a similar feature to handle column re-ordering by the user.

NSView in NSCell

I've read a lot about this but i can't get it to work, i have a custom NSCell with this code
#import "ServiceTableCell.h"
#implementation ServiceTableCell
-(void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView {
NSLog(#"I'm being called");
NSView *newview = [[NSView alloc] initWithFrame:cellFrame];
NSImage *image = [NSImage imageNamed:#"bg.png"];
NSRect imagesize;
NSImageView *IMV = [[NSImageView alloc] initWithFrame:imagesize];
[IMV setImage:image];
[newview addSubview:IMV];
[controlView addSubview:newview];
}
And this my NSTableView data source:
- (long)numberOfRowsInTableView:(NSTableView *)tableView {
return 3;
}
- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(long)row
{
return [[ServiceTableCell alloc] initTextCell:#"dd"];
}
As i understand, the drawwithframe... gets called when the cell is initialized but it never gets called, so, what am I missing?
The method tableView:objectValueForTableColumn:row: should return an object value, not a cell.
Note that NSTableView is substantially different from UITableView, which you may be familiar with. For example, the data source doesn't return cells that are filled with the data, but returns the data. And the cell type in an NSTableView is set per column, you can't have a different kinds of cells in one column (well, technically, that's not entirely true, you could have different cells through -[NSTableColumn dataCellForRow:]).
So thanks to #puzzle answer and a little more digging the answer was to set my subclass of NSCell as the main cell in the InterfaceBuilder, then the method was being called, and as he said, in tableView:objectValueForTableColumn:row: i needed to return the data to then draw it.

NSButton inside NSTableView Programmatically?

How can I set a column of checkboxes inside a NSTableView? So far, this is what I have done, but it just puts the int 1 in the column, not the checkboxes:
-(id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(int)row {
if ([[tableColumn identifier] isEqualToString:#"Enable"]) {
NSButtonCell *cell=[[[NSButtonCell alloc] init] autorelease];
[cell setAllowsMixedState:YES];
[cell setButtonType:NSSwitchButton];
NSCell *aCell = [tableColumn dataCellForRow:row];
[aCell setObjectValue:cell];
return [NSNumber numberWithInt:1];
}
return [[data objectAtIndex:row+1] objectAtIndex:[[data objectAtIndex:0] indexOfObject:[tableColumn identifier]]];
}
you need to use the dataCellForTableColumn delegate method.
- (NSCell *)tableView:(NSTableView *)tableView dataCellForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
if([[tableColumn identifier] isEqualTo:#"delete"]){
NSButtonCell *cell = [[[NSButtonCell alloc] init] autorelease];
[cell setAllowsMixedState:YES];
[(NSButtonCell *)cell setButtonType:NSSwitchButton];
[cell setTitle:#""];
return cell;
}else {
NSCell *cell = [tableColumn dataCell];
return cell;
}
}
In Interface Builder, drag a Check Box Cell to the table column. That will set the data cell for the column. Set up the cell however you want in its Inspector. Then delete all the code you have here that acts on the cell, and just return the NSNumber.
In response to your desire to do this programmatically: Interface Builder is really the way to go here. It's designed for laying out GUI objects. I'll tell you how to do it, though.
First, calling setObjectValue: on an NSCell with another NSCell as the argument doesn't transform the first into the second, or change the pointer values or something like that. The setObjectValue: method changes the objectValue of the cell -- essentially, the object that it displays as text.
If you want to supply a cell for the table column programmatically, you'll need to call -[NSTableColumn setDataCell:] with your desired NSCell somewhere (awakeFromNib would probably be a good choice), but be warned, the table column reuses the same cell instance for every row, and just modifies it before it's drawn so it displays correctly. There's no way to set a different cell for each row in a column, UPDATE: without subclassing NSTableColumn.