objective-ccocoaosx-lionnstextfieldnsoutlineview

NSTextField not calling delegate when inside an NSTableCellView


I have a fairly vanilla Source List (dragged out from the Object Library) in my app, with an NSTreeController as its data source. I set the NSTextField inside the DataCell to be editable, but I want to be able to turn that off for some cells. The way I figured you would do this, is with a delegate for the NSTextField, but none of the delegate methods I've tried get called. Is there something I'm missing? I have the delegate set with an outlet in my XIB, and it happens to be the delegate to the owner NSOutlineView, as well, implementing both the NSOutlineViewDelegate and NSTextFieldDelegate protocols.

Also, I can't use the old –outlineView:shouldEditTableColumn:item: NSOutlineViewDelegate method either, since that only works with cell-based Outline Views (I'm assuming this is the case - the Outline View documentation doesn't appear to have been updated for Lion, though the analogous NSTableView documentation has, and those methods don't get called either).

Update

I reproduced this in a brand new test project, so it's definitely not related to any of my custom classes. Follow the steps below to create my sample project, and reproduce this problem.

  1. In Xcode 4.1, create a new project, of type Mac OS X Cocoa Application, with no special options selected
  2. Create two new files, SourceListDataSource.m and SourceListDelegate.m, with the contents specified below
  3. In MainMenu.xib, drag a Source List onto the Window
  4. Drag two Objects onto the dock (left side of the window), specifying the SourceListDataSource class for one, and the SourceListDelegate for the other
  5. Connect the Outline View's dataSource and delegate outlets to those two objects
  6. Select the Static Text NSTextField for the DataCell view inside the outline view's column
  7. Turn on its Value binding, keeping the default settings
  8. Connect its delegate outlet to the Source List Delegate object
  9. Set its Behavior property to Editable
  10. Build and Run, then click twice on either cell in the outline view.

Expected: The field is not editable, and there is a "well, should I?" message in the log

Actual: The field is editable, and no messages are logged

Is this a bug in the framework, or am I supposed to achieve this a different way?


SourceListDataSource.m

#import <Cocoa/Cocoa.h>

@interface SourceListDataSource : NSObject <NSOutlineViewDataSource>

@property (retain) NSArray *items;

@end

@implementation SourceListDataSource

@synthesize items;

- (id)init
{
    self = [super init];
    if (self) {
        items = [[NSArray arrayWithObjects:@"Alo", @"Homora", nil] retain];
    }

    return self;
}

- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
    if (!item) {
        return [self.items objectAtIndex:index];
    }

    return nil;
}

- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
    return !item ? self.items.count : 0;
}

- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
    return NO;
}

- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
    return item;
}

@end

SourceListDelegate.m

#import <Foundation/Foundation.h>

@interface SourceListDelegate : NSObject <NSOutlineViewDelegate, NSTextFieldDelegate> @end

@implementation SourceListDelegate

- (NSTableRowView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item {
    return [outlineView makeViewWithIdentifier:@"DataCell" owner:self];
}

- (BOOL)control:(NSControl *)control textShouldBeginEditing:(NSText *)fieldEditor {
    NSLog(@"well, should I?");
    return NO;
}

@end

Solution

  • Subclass NSTableCellView, with an outlet for the text field, and set the text field delegate in awakeFromNib. After doing that, control:textShouldBeginEditing: gets called. I'm not sure why, but (edit:) if you set the delegate in the xib, the delegate methods aren't called – I had the same experience as you.

    Alternatively, you can forego the delegate and conditionally set Editable using a binding, either to a boolean property of the model, or using a value transformer which acts on a model instance and returns a boolean. Use the Editable binding of the text field.