I have an NSTableView used as a source list and, alongside it, two editors. When the user selects anything in the table view, its content is opened in the editor that has the focus. When the user Opt-clicks an item in the table, though, the content is opened in the other editor, making it easy for the user to load something in the other editor without having to change the focus first.
This has worked for many years using NSTableView.selectiondDidChange / the NSTableViewDelegate as follows:
func tableViewSelectionDidChange(_ notification: Notification) {
if let event = tableView.window?.currentEvent,
event.type == .leftMouseUp || event.type == .leftMouseDown,
// (Real app does some other checks here too.)
event.modifierFlags.contains(.option) {
openInOtherEditor()
return
}
openInCurrentEditor()
}
However, on macOS 27, it seems that things need to be done differently because of the transition to gesture recognisers for event handling. According to the WWDC video "Modernise Your AppKit App", and to Tech Note TN3212, currentEvent can no longer be relied upon to provide the event that actually triggered an action in NSControl subclasses:
The transition to gesture recognizers on NSControl objects changes the timing of when AppKit delivers control action messages with respect to event processing. As a result, currentEvent no longer returns the event that triggered an action.
It's unclear whether this new limitation refers only to NSControl.action or to all mouse-driven actions, but from the context and what the rest of the Tech Note has to say, I assume it's the latter. (Especially since you are no longer supposed to override mouseDown(with:), and the Console warns about gestures being disabled if you do override mouseDown(with:) in an NSTableView subclass on macOS 27.)
currentEvent still seems to work fine in this situation in the first macOS 27 beta, but it sounds as though we cannot rely on this continuing to be the case.
If we should no longer be using currentEvent, then, what should we use instead to determine whether a selection change was triggered by a mouse click? The Tech Note and WWDC video have nothing to say about this. They simply say that instead of overriding mouseDown(with:), you should use the selection-did-change delegate methods, which is of no help here. (By contrast, checking the modifier flags is still straightforward; the Tech Note says to use NSEvent.modifierFlags instead of currentEvent.modifierFlags.)
Two solutions sprung to mind, but neither worked:
- Check
tableView.clickedRow != -1in theselectionDidChangedelegate method/notification response. This doesn't work, however, becauseclickedRowhas been reset to -1 by the timeNSTableView.selectionDidChangeis sent. - Add an
actionto the table view and checkclickedRowthere. This doesn't work either, though, because althoughclickedRowis available in the action method, I would now have to load content in response to both an action and a selection change, and since the selection changes before the action is called, there is no way of telling my selection-did-change method not to load in the main editor if Option is held down in the action.
The only solution I have found is to override selectRowIndexes(_:byExtendingSelection:), check for clickedRow != -1 there, set a didChangeSelectionWithMouse flag to true if so, and check that in the selection-did-change delegate method. That works, but it's not the most elegant of solutions.
So:
- Am I misunderstanding the Tech Note? Can
currentEventstill in fact be used safely intableViewSelectionDidChange(_:)in macOS 27 and beyond? - If not, what is the recommended way of checking that the table selection has been changed by a mouse click?
Many thanks!