rizwana desai
rizwana desai

Reputation: 21

Find bar is not working when using NSTextView in SwiftUI

I'm attempting to add a Find bar to an NSTextView within a SwiftUI view, but despite setting the necessary properties (usesFindBar, usesFindPanel, isIncrementalSearchingEnabled), the Find bar doesn't appear. Here's the code I'm using:

import AppKit

struct TextView: NSViewRepresentable {

    @Binding var selectedRange: NSRange?
    @Binding var text: String
    var onAddingNewLine: (() -> Void)?
    @Binding var textToReplace: (text: String, range: NSRange)?
    @Binding var shouldInsertText: Bool?

    func makeNSView(context: Context) -> NSScrollView {
        let scrollView = NSTextView.scrollableTextView()
        guard let textView = scrollView.documentView as? NSTextView else {
            return scrollView
        }
        textView.allowsUndo = true
        textView.usesFindBar = true
        textView.usesFindPanel = false
        textView.isIncrementalSearchingEnabled = true
        textView.window?.makeFirstResponder(textView)
        textView.isContinuousSpellCheckingEnabled = true
        textView.font = NSFont.monospacedSystemFont(ofSize: 15, weight: .regular)
        textView.textColor = NSColor(Color.onSurface)
        DispatchQueue.main.async {
            textView.string = text
            textView.delegate = context.coordinator
        }
        return scrollView
    }

    func updateNSView(_ scrollView: NSScrollView, context: Context) {
        guard let textView = scrollView.documentView as? NSTextView else {
            return
        }

        if let textToReplace {
            DispatchQueue.main.async {
                let textToInsert = textToReplace
                self.textToReplace = nil
                if shouldInsertText ?? false {
                    textView.insertText(textToInsert.text, replacementRange: textToInsert.range)
                }
                shouldInsertText = false
            }
        }
    }

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    class Coordinator: NSObject, NSTextViewDelegate {

        var parent: TextView

        init(_ parent: TextView) {
            self.parent = parent
        }

        func textDidEndEditing(_ notification: Notification) {
            guard let textView = notification.object as? NSTextView else { return }
            parent.text = textView.string
            textView.undoManager?.removeAllActions()
        }

        func textDidChange(_ notification: Notification) {
            guard let textView = notification.object as? NSTextView else { return }
            let string = textView.string
            let length = string.count
            let insertionPoint = textView.selectedRange.location
            let low = max(0, insertionPoint - 1)

            if let range = Range(NSRange(location: low, length: 1), in: string) {
                let newChar = string[range]

                if length > 0 && length > parent.text.count, newChar == "\n" {
                    parent.text = textView.string
                    parent.onAddingNewLine?()
                } else {
                    parent.text = textView.string
                }
            } else {
                parent.text = textView.string
            }
        }

        func textViewDidChangeSelection(_ notification: Notification) {
            guard let textView = notification.object as? NSTextView else { return }
            parent.selectedRange = textView.selectedRange
        }
    }

}

This properties(usesFindBar, usesFindPanel, isIncrementalSearchingEnabled) works when using a NSTextView created through Storyboard, but not when using it within SwiftUI.

Also tried out Findbar using this MacEditorTextView

Any insights into why the Find bar isn't showing up would be greatly appreciated!

Upvotes: 0

Views: 91

Answers (1)

Justin Mitchell
Justin Mitchell

Reputation: 11

I was able to get this working by creating this extension

extension NSTextView {
func showFindBar() {
    let menuItem = NSMenuItem(title: "", action: nil, keyEquivalent: "")
    menuItem.tag = NSTextFinder.Action.showFindInterface.rawValue
    self.performTextFinderAction(menuItem)
} 
}

I also moved textView.window?.makeFirstResponder(textView) into DispatchQueue.main.async

Then I just call that showFindBar function from a custom menu bar item tied to the keyboard shortcut F via .commands { CommandGroup(after: .undoRedo) }

Upvotes: 1

Related Questions