MacOS 27 (or perhaps 26, I never used it) introduces two bugs in NSTableView (and delegate). It's always a pleasure when you find that your app no longer works properly.
First, when the delegate responds to tableView:pasteboardWriterForRow:, no row can be selected by click & drag (even if the method returns nil). Which mean that clicking and dragging the mouse on rows that are not draggable simply does nothing (no selection), which is a frustrating user experience, especially considering that the same action selects rows on macOS 15.
The second bug is that tableView.verticalMotionCanBeginDrag = NO is simply ignored. It doesn’t work. So, a click followed by an immediate vertical drag drags the clicked row instead of selecting rows if tableView:pasteboardWriterForRow: does not return nil. Again, it did select rows on macOS 15. That's what verticalMotionCanBeginDrag = NO is for.
To work around the first bug, I implement respondsToSelector: in the delegate to return NO when the selector is tableView:pasteboardWriterForRow: and the clicked row is not draggable. This solution is not elegant, as theclickedRow property of the tableview always returns -1 when the method is called. Which means I need to determine the clicked row by other means (using the mouse location and stuff).
(This solution should in principle allow working around the second bug, but I haven’t implemented it yet. It would require determining the delay between the click and the drag and the direction of the drag).
So my question is: does anyone see a better workaround for these bugs?
I reproduced both issues in macOS 27.0.1 with a small test project of my own, built with Xcode 27.1. They appear in a view-based table view that doesn't override any mouse-handling methods. A vertical drag that starts on a row whose tableView:pasteboardWriterForRow: returns nil selects nothing. Setting verticalMotionCanBeginDrag to NO doesn't stop a vertical drag on a draggable row from starting a drag session. That second result doesn't match the comment in NSTableView.h:
If -verticalMotionCanBeginDrag is YES, then click + a vertical drag of the mouse will drag the clicked item(s). If NO, it will do a "drag select".
Your respondsToSelector: override worked in my tests too. A documented alternative for the first issue is to override canDragRowsWithIndexes:atPoint: in an NSTableView subclass. Returning NO for rows that aren't draggable restored drag selection over those rows. AppKit supplies the mouse-down point, so there's no need for clickedRow or your own hit testing:
- (BOOL)canDragRowsWithIndexes:(NSIndexSet *)rowIndexes atPoint:(NSPoint)mouseDownPoint {
NSInteger row = [self rowAtPoint:mouseDownPoint];
if (!RowIsDraggable(row)) { // Your app's own test.
return NO;
}
return [super canDragRowsWithIndexes:rowIndexes atPoint:mouseDownPoint];
}
That override doesn't affect the second issue. A table view subclass that overrides mouseDown: and calls super made both issues go away in every case I tried, and dragging still worked:
- (void)mouseDown:(NSEvent *)event {
[super mouseDown:event];
}
Adopting gesture recognizers in your app describes the effect of an override like this:
Container views fall back to tracking-loop behavior in the same way when you override their mouse-handling methods.
The same article describes what an app loses with tracking-loop behavior, such as multi-finger gestures on a Mac with a touch display. That's relevant if you're deciding whether to adopt the override.
I suggest filing a separate bug report for each issue, each with a small sample project attached, and posting the FB numbers here. Bug Reporting: How and Why? has tips on creating a report that's easy to act on.