1
votes

I have a simple view based NSTableView with one column which is populated with Core Data entities, and the row's view contains just a NSTextField. I need ONLY the first row to be non editable, and to be displayed in red. I tried to to this somewhere in applicationDidFinishLaunching :

NSView *myView = [myPlaylistsTableView viewAtColumn:0 row:0 makeIfNecessary:NO];

NSArray *mySubviews = [myView subviews];

NSTextField *myTextField = [mySubviews firstObject];

[myTextField setEditable:NO];
NSDictionary *myDictionary = [NSDictionary dictionaryWithObjectsAndKeys:[NSColor redColor],NSForegroundColorAttributeName, nil];
NSAttributedString *myRedAttributedString = [[NSAttributedString alloc] initWithString:@"All Songs" attributes:myDictionary];

[myTextField setAttributedStringValue:myRedAttributedString];

But as soon as a new element is added in the table, or a drag in performed, the first row gets editable again. I have tried to write a value transformer, binding the NSTextField editable binding to the array controller selectionIndex, and return NO if selectionIndex is 0, yes in all other cases. This does not work, with various configurations of the value binding's conditionally set editable checkbox. I guess also a subclass of NSTableView should do the trick but I am a bit lost on this side. Any help is as always greatly appreciated. Thanks.

As suggested by @Joshua Nozzi, I am implementing this delegate method for the table view :

-(NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{

NSView *myView = [tableView makeViewWithIdentifier:[tableColumn identifier] owner:self];

if (tableView == myPlaylistsTableView)
{
if (row == 0)
{
    NSArray *mySubviews = [myView subviews];

    NSTextField *myTextField = [mySubviews firstObject];

    [myTextField setEditable:NO];
    NSDictionary *myDictionary = [NSDictionary dictionaryWithObjectsAndKeys:[NSColor redColor],NSForegroundColorAttributeName, nil];
    NSAttributedString *myBoldAllSongsString = [[NSAttributedString alloc] initWithString:@"All Songs" attributes:myDictionary];

    [myTextField setAttributedStringValue:myBoldAllSongsString];

}
}
return myView;
}

But I must be missing something because the method is called for the correct NSTableView and row = 0, code gets executed but I still don't have a red string and the textfield is editable.

Thanks again for any help.

1

1 Answers

1
votes

You're trying to target a cell view and set it "forever." NSTableView doesn't quite work that way. Each time a row or the entire table is reloaded the views of the affected row(s) are "refreshed".

The appropriate place to customize them is in the NSTableViewDelegate protocol method -tableView:viewForTableColumn:row: (remember to set the controller in which you implement this method as the table view's delegate).

Update Based On OP's Update

Some points:

  1. It's a little weird to use the table column's identifier as your cell view's identifier since the two could be different. Especially if you have more than one possible cell type in the same column (a common scenario). Best to get into the practice of using unique identifiers for each cell view and using the column identifier only to identify the column if you have more than one.

  2. You make your view outside your if() conditional that checks which table is asking. If you have only one table, don't bother checking; if you might have more than one that is using that controller, make sure everything (including -makeViewWithIdentifier:) pertaining to each table is inside its related conditional. Same for returning myView - that should move into your conditional and if no table matches, your last return should be nil for best practice.

  3. You appear to configure your cell view only if row == 0 but you fail to account for a reused view that's already been configured as a row 0 cell but is being reused for a row >0. You may see "interesting" behavior for all rows >0 in a large table that requires much scrolling.

  4. Are you sure the first subview of your cell view is your NSTextField? Do you see any messages in the debugger console (like "...does not respond to selector...")? Or, if you set a breakpoint on your [myTextField setEditable:NO] line, is myTextField nil when the debugger pauses there? Best practice here is to make your own view subclass with IBOutlets to any of its subviews you want to interact with, then update them that way. Relying on its list of subviews is a recipe for confusion and future bugs as you edit your cell view, even if it was working at first.

  5. Related to 4, if all you need is a text field as your cell, why not use the stock NSTableCellView class (which has its own -textField outlet) so you can tell it myView.textField.attributedStringValue = someAttributedString?

  6. NSTextField has individual methods for setting the font and color of its string value. Why not take advantage of those instead of creating and setting an attributed string?

  7. Adding: Forgot you mentioned bindings. Even if you're properly obtaining a reference to your text field, setting its properties here won't help you since the bindings are probably overriding them. Easier to ditch bindings and implement NSTableDataSource protocol alongside NSTableViewDelegate and handle refreshing the table view yourself when your data changes. You'll save yourself a LOT of headaches and confusion regarding the timing of UI updates.

A rewritten example based on the above:

-(NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{

    // Is it the right table view?
    if (tableView == myPlaylistsTableView)
    {

        // Determine properties based on row
        BOOL firstRow = (row == 0);
        NSFont * font = (firstRow) ? [NSFont systemFontOfSize:[NSFont systemFontSize]] : [NSFont boldSystemFontOfSize:[NSFont systemFontSize]];
        NSString * text = (firstRow) ? @"All Songs" : [self.songsList[ row ] songTitle]; // or whatever other row titles will be
        NSColor * color = (firstRow) ? [NSColor redColor] : [NSColor labelColor];

        // Make an configure a cell view
        NSTableCellView * myView = [tableView makeViewWithIdentifier:@"TextCell" owner:nil];
        NSTextField * textField = myView.textField;
        textField.editable = !firstRow;
        textField.stringValue = text;
        textField.font = font;
        textField.textColor = color;

        return myView;

    }

    return nil;
}