Change NSTextView Position

I have a Cocoa project that uses storyboards and auto layout. I have a scroll view as the content view for the view controller. I have a text view inside the scroll view that I want to be able to move around like a sticky note or index card.


I started by overriding the mouseDown and mouseDragged functions for NSTextView. To test if these functions were called, I had them change the text view's background color. I tested the app, and the background color changed when I clicked in the text view and dragged it.


My next step was to change the text view's position when I clicked in it. I wrote the following code:


override open func mouseDown(with event: NSEvent) {
        setFrameOrigin(NSPoint(x: 0, y: 0))
        needsDisplay = true
 }


According to Apple's documentation, calling setFrameOrigin repositions a view inside its superview. In my code sample, clicking inside the text view should change its position to the lower left corner of the superview. But when I test the code and click in the text view, the position of the text view doesn't change. I tried different values for the frame origin, but none of them changed the text view's position. I tried setting the frame origin to the superview's origin and the window's origin, but the position of the text view did not change.


What do I have to do to change the position of a text view?

If you're using auto-layout, setting a view's size or position is going to have no effect, because the next layout pass is going to move it back to wherever its constraints put it. You're going to have to modify the actual constraints at run time.


There are other things that could possibly be wrong. If your content view is a scroll view, then the text view is a descendent view of the scroll view. Where in the view hierarchy did you put it? On macOS, the scroll view has a subview of class NSClipView, and the clip view has a subview that's the "document" view, or actual content. That view determines how far you can scroll. Adding the text view as a subview of the scroll view or clip view is likely going to produce surprising results.


Assuming that you want the text view to scroll with the other scrollable contents, you will need to use a custom view as the document view, and add the text view as a subview of that (along with your other scrollable contents).


If you don't want the text view to scroll with other scrollable contents, the best approach is probably to use a custom view for the content view, and make the scroll view a subview of that. Make the text view a subview of the content view too, so that it's a sibling of the scroll view, not a descendent.


In fact, you may want to wrap the text view in its own custom parent view, too. You could make that the draggable subview, which allows you to avoid subclassing NSTextView.

The text view is a descendant of the clip view's subview.


Thanks for the information on auto layout. I didn't realize auto layout prevented you from changing a view's position.


I had a feeling I was going to need a custom view to do what I want. I was hoping there was a way to move text views around using regular text views and scroll views.

>> The text view is a descendant of the clip view's subview.


In that case, you don't need any additional custom views.


Whether you want additional custom views remains to be seen. (A custom view, in this context, includes the case where you add a view simply as a container for another view. You might wrap a text view this way, for example, to avoid subclassing NSTextView. Cocoa controls and standard views can be a bit quirky, so subclassing can sometimes take you into la-la-land.)


One possible unrelated reason for wrapping the text view is if you want a border around the view that gives something to "grab" to make it draggable without accidentally selecting contents. NSTextView does have built-in border capabilities, but they interact with the way selections are drawn. For visual reasons, you might want an outer border, and wrapping in a custom view is one way of achieving that.

I'm interested in adding a margin to a NSTextView by wrapping it in a custom view or adding sibling views, but wasn't able to get the scroll view to keep showing the entire text view. Would you mind sharing your code?

Change NSTextView Position
 
 
Q