DEV Community

nooraja
nooraja

Posted on

SwiftUI Text Selection vs UITextView: the compatibility gap Apple left behind

Illustration comparing whole-value and range text selection

Text selection is a small feature with a large usability impact. Users often need to copy error messages, transaction IDs, URLs, log output, addresses, or a single sentence from a longer block of text.

SwiftUI makes enabling it delightfully small:

Text("Request failed: 401 Unauthorized")
    .textSelection(.enabled)
Enter fullscreen mode Exit fullscreen mode

However, this modifier has a behaviour change that matters a lot for compatibility. The same source code does not deliver the same selection experience on every supported iOS release.

The short version

Platform / version What .textSelection(.enabled) does
macOS 12+ Lets people select a text range with normal pointer and keyboard conventions.
iOS / iPadOS 15 through 26 Long press exposes a system menu that acts on the whole Text view. No native range handles for selecting only part of that Text.
iOS / iPadOS 27+ Long press shows selection highlight and handles, enabling range selection.

Apple documents this distinction explicitly. The modifier has been available since iOS 15, yet the familiar partial-selection interaction only arrives in iOS 27. That is a frustrating gap for something as basic as selecting two words in a paragraph. Come on, Apple: SwiftUI should not force a UIKit escape hatch for a fundamental text interaction after so many years.

The iOS 27 row reflects Apple’s current documentation. Test it on the target OS and Xcode you are shipping with; do not infer the behaviour just from the modifier compiling.

What SwiftUI gets right

One clear, composable API

VStack(alignment: .leading) {
    Text("Build failed")
        .font(.headline)
    Text(logOutput)
        .font(.system(.body, design: .monospaced))
}
.textSelection(.enabled)
Enter fullscreen mode Exit fullscreen mode

Applying the modifier to a container can enable selection for the contained Text views. That is clean, discoverable SwiftUI code with no subclassing or delegate plumbing.

Great for whole-value copying

On iOS 15–26, the behaviour is still useful when the entire value is what users want: a UUID, an email address, an API key displayed safely, an error message, or an IP address. Apple’s Human Interface Guidelines specifically calls out useful labels such as error messages, locations, and IP addresses as candidates for copyability.

Native accessibility and platform conventions

SwiftUI hands the interaction to the system, which is generally a good default. On macOS in particular, .textSelection(.enabled) feels natural immediately.

Where SwiftUI Text falls short

1. Partial selection on iOS was missing until iOS 27

For a paragraph, log, or legal text, users commonly want only a phrase. On iOS 26 and earlier, SwiftUI treats a selectable Text as a whole-value copy target. There are no selection handles to adjust the range.

This is the central limitation. The modifier can make an app look like it supports selection while delivering a less capable experience on most deployed versions.

2. No selection inside Button

Apple documents that Button views do not support text selection. If a piece of text needs both a tap action and copyable text, redesign the interaction or use a separate control instead of expecting .textSelection to solve both jobs.

3. Limited control over the interaction

Text exposes an opt-in switch, not a text-engine configuration surface. If the product needs precise selection behaviour, custom menus, programmatic ranges, selection-state observation, or highly custom rich-text interaction, SwiftUI’s simple modifier can become too restrictive.

SwiftUI Text versus UIKit UITextView

The useful UIKit comparison is UITextView, not UILabel.

UITextView is a full text component. For read-only selectable content, configure it with isEditable = false and isSelectable = true. It has long supplied UIKit’s standard range-selection UI: selection highlight, drag handles, the loupe in long text, and copy actions.

Unlike Text, it is imperative and heavier. In SwiftUI, it needs a UIViewRepresentable bridge; its container, scrolling, sizing, and accessibility need careful integration. But when the requirement is “select part of this text” on iOS 15–26, it is the reliable native answer.

UILabel is worth mentioning only to avoid a common misconception: it is a display view and has no native selectable-text API. Do not use it as the selection fallback.

Capability SwiftUI Text UIKit UITextView (read-only)
Display static text Excellent Good, but heavier
Enable copy with a small API Yes: .textSelection(.enabled) Yes: configure properties
Range selection on iOS 26 and earlier No, whole Text action only Yes
Rich attributed content Improving, but intentionally higher-level Strong interaction + display support
Selection handles / loupe / standard editing mechanics iOS 27+ for Text ranges Yes
Custom selection interaction Limited Broad UIKit control; can go further with UITextInteraction
SwiftUI layout ergonomics Excellent Requires a wrapper

So the relevant decision is Text vs a read-only UITextView.

The UIKit fallback for older iOS versions

When range selection is a requirement before iOS 27, bridge a non-editable UITextView into SwiftUI:

import SwiftUI
import UIKit

struct SelectableTextView: UIViewRepresentable {
    let attributedText: NSAttributedString

    func makeUIView(context: Context) -> UITextView {
        let textView = UITextView()
        textView.isEditable = false
        textView.isSelectable = true
        textView.isScrollEnabled = false
        textView.backgroundColor = .clear
        textView.textContainerInset = .zero
        textView.textContainer.lineFragmentPadding = 0
        textView.adjustsFontForContentSizeCategory = true
        return textView
    }

    func updateUIView(_ textView: UITextView, context: Context) {
        textView.attributedText = attributedText
    }
}
Enter fullscreen mode Exit fullscreen mode

Usage:

SelectableTextView(
    attributedText: NSAttributedString(
        string: longReleaseNote,
        attributes: [
            .font: UIFont.preferredFont(forTextStyle: .body),
            .foregroundColor: UIColor.label
        ]
    )
)
Enter fullscreen mode Exit fullscreen mode

UITextView has more layout and accessibility details to verify than Text: Dynamic Type, text-container sizing, links, scrolling, ScrollView nesting, and content compression. It is not as elegant, but it uses the mature UIKit text system that already supplies standard selection UI for native text views.

For custom UIKit text components, UITextInteraction can provide the system’s non-editable selection behaviour, including the selection UI and gestures, as long as the custom view implements the required text-input machinery.

A pragmatic compatibility strategy

Choose the implementation based on the user task, not ideology:

if #available(iOS 27.0, *) {
    Text(articleBody)
        .textSelection(.enabled)
} else {
    SelectableTextView(
        attributedText: NSAttributedString(string: articleBody)
    )
}
Enter fullscreen mode Exit fullscreen mode

Use this strategy only when partial selection is a real requirement. A UITextView bridge is unnecessary complexity for a one-line order ID where copying the entire value is correct.

Decision guide

  • Short, whole-value content: use Text with .textSelection(.enabled).
  • macOS app: native SwiftUI text selection is usually the simplest choice.
  • Long text where iOS 15–26 users must select a phrase: use a read-only UITextView wrapper.
  • Custom interaction or selection menus: start from UIKit; evaluate UITextInteraction if UITextView is not enough.
  • Text inside a button: separate the button action from the copyable text.

Final thought

SwiftUI’s API is wonderfully small, and that is exactly why many teams reach for it first. But simplicity is only a win when the behaviour meets the platform expectation. Until iOS 27, .textSelection(.enabled) on iPhone is closer to “make this whole value copyable” than “let the user select text.”

That distinction belongs in product requirements, compatibility testing, and code review. Apple has finally closed the gap in the documented iOS 27 behaviour—but it took far too long for such a fundamental capability.

Sources

Top comments (0)