Arm1.ru

How to get selected text inside of TextEditor in SwiftUI on macOS

How to get selected text inside of TextEditor in SwiftUI on macOS

TextEditor in SwiftUI still doesn’t have any API to get user selection. But since it’s using NSTextView internally we can subscribe to its notifications.

Here’s an example of getting a substring from user selection:

Before macOS 15:

import SwiftUI

struct ContentView: View {
    @State var myString = "Hello world"
    @State var selectedString = ""

    var body: some View {
        HStack {
            Text("Selection:")
                .foregroundStyle(.secondary)
            Text("\"\(selectedString)\"")
        }.padding()

        TextEditor(text: $myString)
            .font(.title)
            .onReceive(NotificationCenter.default.publisher(
                for: NSTextView.didChangeSelectionNotification
            ), perform: { notification in
                guard let textView = notification.object as? NSTextView else { return }

                // check, if it's the one we need
                guard textView.string == myString else { return }

                let selectionRage = textView.selectedRange()

                let start = String.Index(
                    utf16Offset: selectionRage.lowerBound, in: myString
                )
                let end = String.Index(
                    utf16Offset: selectionRage.upperBound, in: myString
                )
                let subStringRange: Range<String.Index> = start..<end

                selectedString = String(myString[subStringRange])
        })
    }
}

On macOS 15+ there's a new TextSelection struct which can be used in .onChange subscriber:

import SwiftUI

struct ContentView: View {
    @State var myString = "Hello world"
    @State var selectedString = ""
    @State private var selection: TextSelection?

    var body: some View {
        HStack {
            Text("Selection:")
                .foregroundStyle(.secondary)
            Text("\"\(selectedString)\"")
        }.padding()

        TextEditor(text: $myString, selection: $selection)
            .font(.title)
            .onChange(of: selection) {
                if let selection = selection.take() {
                    if case let .selection(range) = selection.indices {
                        selectedString = String(myString[range])
                    }
                }
            }
    }
}
keyboard_return back
local_offer swiftui macos