How to override NSTextView dragging behaviour without overriding mouseDown:?

I have an NSTextView subclass that overrides mouseDown: to allow for image resizing. If a user clicks and drags on the edges of an image, I implement custom behaviour that resizes the image (and shows resizing cursors). If the user clicks anywhere else, super's implementation is called. This all works great.

As of macOS 27, however, the transition to gesture recognisers instead of overriding mouseDown: means that I should probably be moving away from the above approach. NSTextView now uses the new NSTextSelectionManager to implement selection and dragging via gesture recognisers, although, according to the release notes:

Existing NSTextView subclasses that override mouseDown: continue to work through a binary-compatible fallback path. (163365571)

It's unclear whether this means that we therefore should still override mouseDown: for custom behaviour in NSTextView, but to me, this, along with the content of Tech Note TN3212, strongly implies that, although it will continue to work thanks to "a binary-compatible fallback", we should entirely move away from overriding mouseDown: in the future.

If that is indeed the case, how do we implement custom dragging behaviour--such as for resizing images as in my example--in NSTextView? There still seems to be no way of doing it other than overriding mouseDown:.

I had thought that I might be able to add an NSPanGestureRecognizer to the text view and have it fail via its delegate methods if the clicks were outside of an image's edges, but a pan gesture recogniser added to an NSTextView is entirely ignored, presumably because of the private gestures already added.

Fortunately everything continues to work for now, but I would like to update my code as much as possible.

I'm really glad to hear that you're working on adopting gesture recognizers and exploring NSTextSelectionManager.

Quick clarification that I think matters here. On macOS 27 the selection interactions aren't handled by NSTextView's own event methods anymore. They're owned by NSTextSelectionManager, which installs and manages its own gesture recognizers on the text view. You can get to the manager through the new NSView.textSelectionManager property. Your pan recognizer got ignored because those selection recognizers begin right away and nothing told them to wait for yours, so yours never had a chance to start.

mouseDown: is still supported, not deprecated. What it does under the hood is worth knowing though. NSTextView sees the override and disables the NSTextSelectionManager gesture path for the whole view, falling back to the old NSEvent selection code. That's why your call-super path still behaves the way it always did. You also still need the override if you're deploying to anything older than 27, since the gesture path isn't there. The annoying part is that currently there isn't a great way to keep the override only for the older releases and still get the gesture-based selection on 27, because overriding the method is all-or-nothing per view. The best way to do this currently is providing different subclasses depending on what OS your running on, but ugh. We're still working on a better answer though so stay tuned.

How you move to gesture recognizers depends on how the image is in the view today.

If you're already vending the image through NSTextAttachmentViewProvider, you're in good shape. The attachment has its own view, so just add your resize recognizer to that view. A recognizer on a subview takes precedence over selection on its own, and selection everywhere else keeps working.

If you're drawing the image yourself or using an attachment cell, the cleanest move is to adopt NSTextAttachmentViewProvider so the image gets its own view, then do the above. That's what I'd recommend if it's at all practical for you.

If moving the image into its own view isn't an option and you need to keep drawing into the text view, you can add your own recognizer in your subclass. IMO a press gesture matches "drag on an edge" better than a pan here but you'll need to set up failure relationship yourself so selection waits on it. NSTextSelectionManager hands you its recognizers for this:

for (NSGestureRecognizer *selectionGesture in self.textSelectionManager.gesturesForFailureRequirements) {
    [selectionGesture requireGestureRecognizerToFail:myEdgeResizeGesture];
}

Then have myEdgeResizeGesture begin only when the press is on an image edge (gate it in gestureRecognizerShouldBegin: or the delegate), so it fails everywhere else and selection runs as normal. Skip the failure relationship and the selection recognizers just win, which is the behavior you ran into.

Thank you for such a fantastic and thorough answer! That covers pretty much everything I was missing and I really appreciate it.

You can get to the manager through the new NSView.textSelectionManager property.

I had been looking for this but in the wrong place - I was looking in NSTextView and NSTextInputClient and hadn't thought to look on NSView. That's great that this exists.

If you're already vending the image through NSTextAttachmentViewProvider, you're in good shape.

Unfortunately I'm unable to use NSTextAttachmentViewProvider, as I believe it is TextKit 2 only, whereas for now I need to use TextKit 1 until TK2 catches up a bit more (multiple text containers, table support and so on).

(Hmm, actually, is NSTextSelectionManager even used in TextKit 1? I notice its data source methods all use TK2 ranges and locations.)

After a quick test in a sample project, though, it looks like your suggestion of using textSelectionManager.gesturesForFailureRequirements should work perfectly. It seems that requireGestureRecognizerToFail: is only available in UIKit, not AppKit, but I was able to achieve the same effect using:

- (BOOL)gestureRecognizer:(NSGestureRecognizer *)gestureRecognizer shouldBeRequiredToFailByGestureRecognizer:(NSGestureRecognizer *)otherGestureRecognizer
{
    return [self.textSelectionManager.gesturesForFailureRequirements containsObject:otherGestureRecognizer];
}

One question regarding this though: a class dump reveals that NSTextView already implements several NSGestureRecognizerDelegate methods, which presumably means that if I implement the delegate methods myself in my subclass, I risk breaking standard behaviour. So am I right in thinking that I should avoid making my NSTextView subclass the delegate of my own gesture recogniser?

(Is there a reason NSTextView doesn't publicly declare the NSGestureRecognizerDelegate methods it conforms to so that we can override them? I notice it's the same with other views migrating to gesture recognisers, such as NSTableView. Sorry, that was two questions.)

The best way to do this currently is providing different subclasses depending on what OS your running on, but ugh. We're still working on a better answer though so stay tuned.

This is all really useful information, thanks. Given that I create my text views programmatically, I could use different subclasses, but I'll probably just get gesture recognisers working (so that I'm ready for the transition), and then stick to using mouseDown: for the time being, for backward compatibility.

Thanks again!

How to override NSTextView dragging behaviour without overriding mouseDown:?
 
 
Q