DEV Community

Cover image for Text Selection in SwiftUI TextEditor Is Still Harder Than It Should Be
nooraja
nooraja

Posted on

Text Selection in SwiftUI TextEditor Is Still Harder Than It Should Be

TextEditor looks wonderfully simple:

TextEditor(text: $note)
Enter fullscreen mode Exit fullscreen mode

People can type multiple lines, move the caret, long press, select text, and copy or paste. For an ordinary notes field, that is excellent.

The trouble begins when the app itself needs to understand that selection.

What if a Bold toolbar action should activate only when text is selected? What if a template must be inserted at the caret? Or if suggestions depend on the word the user has highlighted?

At that point, SwiftUI's TextEditor can feel more difficult than long-established UIKit controls.

Selection UI is not selection state

First, an important distinction: users can select text directly in TextEditor. iOS supplies the familiar caret, selection handles, and system Copy/Paste menu.

The historical gap was accessing the state of that selection from SwiftUI. The basic TextEditor(text:) API binds only the editor's contents to a String:

@State private var note = "Select part of this text."

TextEditor(text: $note)
Enter fullscreen mode Exit fullscreen mode

We know the value of note, but not whether the user is at character 5 or has selected characters 5 through 20. That small distinction matters enormously to a feature-rich editor.

The good news: TextSelection in iOS 18

SwiftUI now has an official answer: TextSelection and the TextEditor(text:selection:) initializer in iOS 18.

import SwiftUI

struct NoteEditor: View {
    @State private var text = "Select part of this text."
    @State private var selection: TextSelection?

    var body: some View {
        TextEditor(text: $text, selection: $selection)
            .frame(minHeight: 180)
    }
}
Enter fullscreen mode Exit fullscreen mode

TextSelection can represent an insertion point (the caret) or a range of text. An app can therefore respond to a user's selection without inspecting the UIKit view behind SwiftUI.

This is meaningful progress, but it has two practical consequences:

  1. The API requires iOS 18 or later.
  2. It does not remove the need for UIKit when an editor is highly customized or must support older iOS versions.

Why UIKit feels more direct

UIKit has had a mature text-input model for a long time through UITextInput. Both UITextField and UITextView expose selectedTextRange; for multiline editing, UITextView also exposes selectedRange as an NSRange.

Here is how to select all text in a UITextField:

let field = UITextField()
field.text = "INV-2026-0817"

if let range = field.textRange(
    from: field.beginningOfDocument,
    to: field.endOfDocument
) {
    field.selectedTextRange = range
}
Enter fullscreen mode Exit fullscreen mode

In UIKit, selection has long been part of the public API surface. You can read the active range, change it, and receive notifications when it changes.

One important note: the correct UIKit counterpart to a multiline TextEditor is UITextView, not UITextField. UITextField is still a useful example because both controls use the same UITextInput model.

Need SwiftUI TextEditor UIKit
User selection with native iOS UI Yes Yes
Bind the text value Binding<String> text plus target/delegate
Read or change selection in code TextSelection on iOS 18+ selectedTextRange / selectedRange
Multiline editing Yes UITextView
Support selection state on older iOS Needs another strategy Long-standing API support
Highly customized input behavior More limited Broader delegate and UITextInput control

The fallback: wrap UITextView

If your deployment target includes iOS 17 or earlier and selection is a product requirement, the straightforward fallback is a UITextView in UIViewRepresentable.

struct UIKitEditor: UIViewRepresentable {
    @Binding var text: String
    @Binding var selectedRange: NSRange

    func makeUIView(context: Context) -> UITextView {
        let view = UITextView()
        view.delegate = context.coordinator
        return view
    }

    func updateUIView(_ view: UITextView, context: Context) {
        if view.text != text { view.text = text }
        if view.selectedRange != selectedRange {
            view.selectedRange = selectedRange
        }
    }

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

    final class Coordinator: NSObject, UITextViewDelegate {
        var parent: UIKitEditor
        init(_ parent: UIKitEditor) { self.parent = parent }

        func textViewDidChange(_ textView: UITextView) {
            parent.text = textView.text
        }

        func textViewDidChangeSelection(_ textView: UITextView) {
            parent.selectedRange = textView.selectedRange
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

The wrapper delivers the required control, but it comes with cost: two-way synchronization, focus management, accessibility, Dynamic Type, and range validation whenever text changes.

One detail is easy to miss: NSRange uses UTF-16 offsets, not Swift Character counts. Emoji and combined characters can make index conversion a source of bugs. Do not assume that NSRange(location: 2, length: 1) simply means “the third character.”

A gap that is still noticeable after SwiftUI's 2019 debut

Apple introduced SwiftUI in 2019 and has clearly continued to invest in it. TextSelection in iOS 18 shows that Apple recognizes modern editor requirements.

Still, the gap is not entirely closed:

  • The modern selection-state API arrived only in iOS 18, so it does not solve the problem for apps that support older systems.
  • Developers of feature-rich editors still often need UIKit's deeper capabilities.
  • Bridging to UITextView increases the size and complexity of a codebase for behavior that has long been fundamental to UIKit text input.

The fair criticism is not “SwiftUI cannot select text.” Users can select text in TextEditor. The more precise criticism is this: observing and controlling selection from SwiftUI arrived late, and it still does not fully replace UIKit's depth.

Practical recommendation

  • Use TextEditor(text:) for a simple note editor.
  • Use TextEditor(text:selection:) when iOS 18 is your minimum target and the app needs selection state.
  • Use a UITextView wrapper when you must support older iOS versions or need a highly customized editor.
  • Do not use UITextField for multiline content; use UITextView.

SwiftUI makes many things more elegant. For advanced text editing, however, UIKit remains an important escape hatch—which feels a little ironic after years of Apple investment in SwiftUI.

References

Top comments (0)