kyle-n / HighlightedTextEditor

A SwiftUI view for dynamically highlighting user input
MIT License
719 stars 68 forks source link

Multiple menu items rendering while typing in HLTE #56

Closed niravbhimani53 closed 2 years ago

niravbhimani53 commented 2 years ago

Is your feature request related to a problem? Please describe. Problem: I am trying to add a custom menu item when the user right-clicks inside the HLTE empty text area or text and I am achieving that by adding menu items in the .introspect API since I can access editor. But as I begin typing the number of menu items keeps on increasing as I type into the editor. For example, if I type 10 words in HLTE, the right menu inside the editor will render 10 Print some stuff items (screenshot below).

HighlightedTextEditor(text: $text, highlightRules: .markdown)
            .onTextChange { print("latest text value", $0) }
            .onSelectionChange { (range: NSRange) in
                print("range \(range)")
            }
            .introspect { editor in
                let printMenuItem = NSMenuItem()
                printMenuItem.title = "Print some stuff"
                editor.textView.menu?.addItem(printMenuItem)
            }

NOTE: I'm using the example project available in this repo Essayist.

Describe the solution you'd like Maybe an HLTE API to instantiate menu items one time only.

Describe alternatives you've considered N/A

Additional context

Screen Shot 2022-01-06 at 10 53 18 pm
kyle-n commented 2 years ago

OK, you've run into an interesting problem. TLDR .introspect() runs every time SwiftUI updates the HLTE view.

HLTE operates on SwiftUI logic. Every time its view is redrawn, it will redraw the component, re-highlight the text and re-run the modifiers. This is how all SwiftUI views work - your UI is an expression of your state. When the state changes, the view is redrawn and its modifiers re-run. This is all declarative UI, yadda yadda yadda.

This is why HLTE runs .introspect() every time SwiftUI updates. If you're using .introspect() to set the background color from an @State variable, you need .introspect() to run every time that variable changes.

In your case, you are dipping down into AppKit (by necessity) to add those right-click options. AppKit, of course, is imperative and not declarative. So this .introspect() function is imperatively adding an option every time the view is redrawn.

I don't plan to change HLTE to address this, because again, .introspect() should run every time the view changes. Instead, I would recommend checking the textView.menu in your .introspect() to see if it already has the item you want to add.

niravbhimani53 commented 2 years ago

Cheers