5
votes

I'm facing a problem with a view-based NSTableView running on 10.8 (target is 10.7, but I think this is not relevant).

I'm using an NSTableView, and I get content values for my custom NSTableCellView through bindings. I use the obejctValue of the NSTableCellView to get my data.

I added a button to my cell, and I'd like it to trigger some action when clicked. So far I have only been able to trigger an action within the custom NSTableCellView's subclass.

I can get the row that was clicked like this, using the chain:

NSButton *myButton = (NSButton*)sender;    

NSTableView *myView = (NSTableView*)myButton.superview.superview.superview;

NSInteger rowClicked = [myView rowForView:myButton.superview];

From there I don't know how to reach my App Delegate or controller where the action is defined.

As I am using cocoa bindings, I do not have a delegate on the NSTableView that I could use to trigger my action.

Do you have any idea how I could talked back to controller ?

Many thanks in advance!

4

4 Answers

3
votes

Although you are using bindings you can still set your controller as the delegate for your tableview in the interface builder.

I see that you already are able to access the table view from inside your cell. The next task must be simple, just set the table view delegate as the target for your button's action.

3
votes

Thanks for your question, I also will be triggering an action from a button on a NSTableView. Your question helped to put me on the correct path.

First to address the your solution to finding which row number my NSTableView is on. I was able to find it without knowing the button, in my custom NSTableView I installed the following as a first attempt:

- (NSInteger)myRowNumber  
{  
    return [(NSTableView*)self.superview.superview rowForView:self];  
}

this works fine, however it is less than robust. It only works if you already know specifically how deep you are in the view hierarchy. A more robust and universal solution is:

- (NSInteger)myRowNumber  
{   
    NSTableView* tableView = nil;   
    NSView* mySuperview = self;   

    do   
    {   
        NSView* nextSuper = mySuperview.superview;   
        if (nextSuper == nil)   
        {   
            NSException *exception =   
                [NSException exceptionWithName:@"NSTableView not found."   
                    reason:[NSString stringWithFormat:@"%@ search went too deep.",   
                    NSStringFromSelector(_cmd)] userInfo:nil];   
            @throw exception;   
        }   

        if ([nextSuper isKindOfClass:[NSTableView class]])   
            tableView = (NSTableView*)nextSuper;   
        else   
            mySuperview = mySuperview.superview;   
    } while (tableView == nil);   

    return [tableView rowForView:self];   
}   

this not only works at the NSTableView level, but works with anything installed at any level above it, no matter how complex the view hierarchy.

As to the unanswered part of your question, I established an IBOutlet in my class and using interface builder tied if to my files owner (in my case my document class). Once I had a reference to the class I was sending my message to, and the row number, I call the function. In my case the call required that I pass the row number it originates from.

[self.myDoc doSomethingToRow:self.myRowNumber];   

I tested this and it works at various levels of the view hierarchy above NSTableView. And it functions without having to have the row selected first (which appears to be assumed in Apples documentation).

Regards, George Lawrence Storm, Maltby, Washington, USA

2
votes

Use rowForView: and the responder chain

To respond to a control's action embedded within an NSTableCellView, the control should issue the action to the First Responder. Alternatively, File Owner is possible but this is more tightly coupled.

Use rowForView: within the action method to determine which row's control issued the action:

- (IBAction)revealInFinder:(id)sender {
    NSInteger row = [self.tableView rowForView:sender];
    ...
}

The action is implemented within any of the responder chain classes. Most likely, this will be your subclassed NSWindowController instance. The responder could also be the application delegate; assuming the delegate has a means to talk to the NSTableView.

See Apple's example TableViewPlayground: Using View-Based NSTableView and NSOutlineView to see this in action.

0
votes

Suhas answer helped me.

func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {

    if let cell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "EDIT_CELL_VIEW"), owner: self) as? SymbolManagerCell  {                    
                if let editButton = cell.subviews[0] as? NSButton {
                    editButton.target = cell // this is required to trigger action
                }
                return cell
            }
    return nil
}