1

I have a NSViewRepresentable that contains an NSScrollView with a NSTextView inside.

struct MultilineTextField: NSViewRepresentable {
    typealias NSViewType = NSScrollView
    private let scrollView = NSScrollView()
    private let textView = NSTextView()
    @Binding var text: String
    @Binding var loc: NSRange
    func makeNSView(context: Context) -> NSScrollView {
        textView.string = text
        textView.delegate = context.coordinator
        scrollView.documentView = textView
        return scrollView
    }
    func updateNSView(_ scrollView: NSScrollView, context: Context) {}
    func makeCoordinator() -> Coordinator {
        return Coordinator(self)
    }
    class Coordinator: NSObject, NSTextViewDelegate {
        let textField: MultilineTextField
        init(_ textField: MultilineTextField) {
            self.textField = textField
        }
        func textDidChange(_ notification: Notification) {
            textField.text = textField.textView.string
        }
        func textViewDidChangeSelection(_ notification: Notification) {
            DispatchQueue.main.async {
                self.textField.loc = self.textField.textView.selectedRange()
            }
        }
    }
}

I'm trying to extract the selectedRange of the NSTextView to be displayed in a different view, eg:

Text(String(loc.location))

It works when the selectedRange is changed with arrow keys and on mouse down and mouse up, but it seems like textViewDidChangeSelection doesn't get called when in the middle of dragging the mouse over the text. As a result, the displayed loc.location value doesn't change while dragging until the mouse is lifted up.

As a workaround, I've tried subclassing NSTextView to override the mouseMoved method, but it seems like that doesn't get called either for some reason.

Just to verify that selectedRange actually gets updated on mouse drag, I tried continually updating loc (this is in the Coordinator class):

init(_ textField: MultilineTextField) {
    self.textField = textField
    super.init()
    updateSelection()
}
func updateSelection() {
    DispatchQueue.main.async {
        self.textField.loc = self.textField.textView.selectedRange()
        self.updateSelection()
    }
}

This code does work, but also uses 100% CPU for no reason.

Is there a way to be notified when selectedRange changes in the case of mouse dragging?

bjb568
  • 11,089
  • 11
  • 50
  • 71
  • @Asperi It would change when dragging backwards, but either way `loc.length` doesn't change either. – bjb568 Dec 27 '20 at 13:02
  • As far as I see the representable is designed incorrectly - NS* properties will be recreated every time view is updated. See next for working approach, which you can extend for your needs https://stackoverflow.com/a/63761738/12299030. – Asperi Dec 27 '20 at 14:08

0 Answers0