NSTableView: detecting a mouse click together with the row and column

There is a simple way.

Tested with Swift 3.0.2 on macOS 10.12.2 and Xcode 8.2.1

Let

tableView.action = #selector(onItemClicked)

Then

@objc private func onItemClicked() {
    print("row \(tableView.clickedRow), col \(tableView.clickedColumn) clicked")
}

I would prefer doing as follows.

Override

-(BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(NSInteger)row;

Provide super implementation;

RequiredRow = row;
RequiredColumn = [tableView clickedColumn];

Hope this helps.


To catch the user clicking a row (only, when the user clicks a row, not when it is selected programmatically) :

Subclass your NSTableView and declare a protocol

MyTableView.h

@protocol ExtendedTableViewDelegate <NSObject>

- (void)tableView:(NSTableView *)tableView didClickedRow:(NSInteger)row;

@end

@interface MyTableView : NSTableView

@property (nonatomic, weak) id<ExtendedTableViewDelegate> extendedDelegate;

@end

MyTableView.m

Handle the mouse down event (note, the delegate callback is not called when the user clicks outside, maybe you want to handle that too, in that case, just comment out the condition "if (clickedRow != -1)")

- (void)mouseDown:(NSEvent *)theEvent {

    NSPoint globalLocation = [theEvent locationInWindow];
    NSPoint localLocation = [self convertPoint:globalLocation fromView:nil];
    NSInteger clickedRow = [self rowAtPoint:localLocation];

    [super mouseDown:theEvent];

    if (clickedRow != -1) {
        [self.extendedDelegate tableView:self didClickedRow:clickedRow];
    }
}

Make your WC, VC conform to ExtendedTableViewDelegate.

@interface MyViewController : DocumentBaseViewController<ExtendedTableViewDelegate, NSTableViewDelegate,  NSTableViewDataSource>

set the extendedDelegate of the MyTableView to your WC, VC (MyViewController)

somewhere in MyTableView.m

self.myTableView.extendedDelegate = self

Implement the callback in delegate (MyViewController.m)

- (void)tableView:(NSTableView *)tableView didClickedRow:(NSInteger)row {
    // have fun
}

If someone is looking for a Swift 3/4/5 version of Peter Lapisu's answer:

Add an extension for the NSTableView (NSTableView+Clickable.swift):

import Foundation
import Cocoa

extension NSTableView {
    open override func mouseDown(with event: NSEvent) {
        let globalLocation = event.locationInWindow
        let localLocation = self.convert(globalLocation, from: nil)
        let clickedRow = self.row(at: localLocation)

        super.mouseDown(with: event)

        if (clickedRow != -1) {
            (self.delegate as? NSTableViewClickableDelegate)?.tableView(self, didClickRow: clickedRow)
        }
    }
}

protocol NSTableViewClickableDelegate: NSTableViewDelegate {
    func tableView(_ tableView: NSTableView, didClickRow row: Int)
}

Then to use it, make sure you implement the new delegate protocol:

extension MyViewController: NSTableViewClickableDelegate {
    @nonobjc func tableView(_ tableView: NSTableView, didClickRow row: Int) {
        Swift.print("Clicked row \(row)")
    }
}

The @nonobjc attribute silences the warning about it being close to didClick.