<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: nooraja</title>
    <description>The latest articles on DEV Community by nooraja (@nooraja).</description>
    <link>https://dev.to/nooraja</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F550858%2Fd0c5812d-591a-4062-801b-0edcddd35e7f.jpeg</url>
      <title>DEV Community: nooraja</title>
      <link>https://dev.to/nooraja</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nooraja"/>
    <language>en</language>
    <item>
      <title>Text Selection in SwiftUI TextEditor Is Still Harder Than It Should Be</title>
      <dc:creator>nooraja</dc:creator>
      <pubDate>Thu, 27 Aug 2026 13:34:25 +0000</pubDate>
      <link>https://dev.to/nooraja/text-selection-in-swiftui-texteditor-is-still-harder-than-it-should-be-59ke</link>
      <guid>https://dev.to/nooraja/text-selection-in-swiftui-texteditor-is-still-harder-than-it-should-be-59ke</guid>
      <description>&lt;p&gt;&lt;code&gt;TextEditor&lt;/code&gt; looks wonderfully simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kt"&gt;TextEditor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;$note&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;The trouble begins when the app itself needs to understand that selection.&lt;/p&gt;

&lt;p&gt;What if a &lt;strong&gt;Bold&lt;/strong&gt; 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?&lt;/p&gt;

&lt;p&gt;At that point, SwiftUI's &lt;code&gt;TextEditor&lt;/code&gt; can feel more difficult than long-established UIKit controls.&lt;/p&gt;

&lt;h2&gt;
  
  
  Selection UI is not selection state
&lt;/h2&gt;

&lt;p&gt;First, an important distinction: users &lt;strong&gt;can&lt;/strong&gt; select text directly in &lt;code&gt;TextEditor&lt;/code&gt;. iOS supplies the familiar caret, selection handles, and system Copy/Paste menu.&lt;/p&gt;

&lt;p&gt;The historical gap was accessing the &lt;strong&gt;state&lt;/strong&gt; of that selection from SwiftUI. The basic &lt;code&gt;TextEditor(text:)&lt;/code&gt; API binds only the editor's contents to a &lt;code&gt;String&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;@State&lt;/span&gt; &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;note&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Select part of this text."&lt;/span&gt;

&lt;span class="kt"&gt;TextEditor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;$note&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We know the value of &lt;code&gt;note&lt;/code&gt;, 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  The good news: &lt;code&gt;TextSelection&lt;/code&gt; in iOS 18
&lt;/h2&gt;

&lt;p&gt;SwiftUI now has an official answer: &lt;code&gt;TextSelection&lt;/code&gt; and the &lt;code&gt;TextEditor(text:selection:)&lt;/code&gt; initializer in iOS 18.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;SwiftUI&lt;/span&gt;

&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;NoteEditor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;@State&lt;/span&gt; &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Select part of this text."&lt;/span&gt;
    &lt;span class="kd"&gt;@State&lt;/span&gt; &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;selection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;TextSelection&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;TextEditor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;$text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;selection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;$selection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;minHeight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;180&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;TextSelection&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;This is meaningful progress, but it has two practical consequences:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The API requires iOS 18 or later.&lt;/li&gt;
&lt;li&gt;It does not remove the need for UIKit when an editor is highly customized or must support older iOS versions.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why UIKit feels more direct
&lt;/h2&gt;

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

&lt;p&gt;Here is how to select all text in a &lt;code&gt;UITextField&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;field&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;UITextField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"INV-2026-0817"&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;range&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;textRange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;beginningOfDocument&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;endOfDocument&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;selectedTextRange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;range&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;One important note: the correct UIKit counterpart to a multiline &lt;code&gt;TextEditor&lt;/code&gt; is &lt;strong&gt;&lt;code&gt;UITextView&lt;/code&gt;&lt;/strong&gt;, not &lt;code&gt;UITextField&lt;/code&gt;. &lt;code&gt;UITextField&lt;/code&gt; is still a useful example because both controls use the same &lt;code&gt;UITextInput&lt;/code&gt; model.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Need&lt;/th&gt;
&lt;th&gt;SwiftUI &lt;code&gt;TextEditor&lt;/code&gt;
&lt;/th&gt;
&lt;th&gt;UIKit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;User selection with native iOS UI&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bind the text value&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Binding&amp;lt;String&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;text&lt;/code&gt; plus target/delegate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Read or change selection in code&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;TextSelection&lt;/code&gt; on iOS 18+&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;selectedTextRange&lt;/code&gt; / &lt;code&gt;selectedRange&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multiline editing&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;&lt;code&gt;UITextView&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Support selection state on older iOS&lt;/td&gt;
&lt;td&gt;Needs another strategy&lt;/td&gt;
&lt;td&gt;Long-standing API support&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Highly customized input behavior&lt;/td&gt;
&lt;td&gt;More limited&lt;/td&gt;
&lt;td&gt;Broader delegate and &lt;code&gt;UITextInput&lt;/code&gt; control&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The fallback: wrap &lt;code&gt;UITextView&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;If your deployment target includes iOS 17 or earlier and selection is a product requirement, the straightforward fallback is a &lt;code&gt;UITextView&lt;/code&gt; in &lt;code&gt;UIViewRepresentable&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;UIKitEditor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UIViewRepresentable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;@Binding&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;
    &lt;span class="kd"&gt;@Binding&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;selectedRange&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;NSRange&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;makeUIView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;UITextView&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;view&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;UITextView&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;delegate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;coordinator&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;updateUIView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;view&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UITextView&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;selectedRange&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;selectedRange&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;selectedRange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;selectedRange&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;makeCoordinator&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Coordinator&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kt"&gt;Coordinator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;Coordinator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;NSObject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;UITextViewDelegate&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UIKitEditor&lt;/span&gt;
        &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UIKitEditor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;textViewDidChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;textView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UITextView&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;textView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;textViewDidChangeSelection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;textView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UITextView&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;selectedRange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;textView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;selectedRange&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  A gap that is still noticeable after SwiftUI's 2019 debut
&lt;/h2&gt;

&lt;p&gt;Apple introduced SwiftUI in 2019 and has clearly continued to invest in it. &lt;code&gt;TextSelection&lt;/code&gt; in iOS 18 shows that Apple recognizes modern editor requirements.&lt;/p&gt;

&lt;p&gt;Still, the gap is not entirely closed:&lt;/p&gt;

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

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

&lt;h2&gt;
  
  
  Practical recommendation
&lt;/h2&gt;

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

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/documentation/swiftui/textselection" rel="noopener noreferrer"&gt;Apple — TextSelection&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/documentation/swiftui/texteditor" rel="noopener noreferrer"&gt;Apple — TextEditor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/documentation/uikit/uitextinput" rel="noopener noreferrer"&gt;Apple — UITextInput&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/documentation/uikit/uitextinput/selectedtextrange" rel="noopener noreferrer"&gt;Apple — selectedTextRange&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/documentation/uikit/uitextview/selectedrange" rel="noopener noreferrer"&gt;Apple — UITextView.selectedRange&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>swiftui</category>
      <category>ios</category>
      <category>uikit</category>
      <category>apple</category>
    </item>
    <item>
      <title>SwiftUI Text Selection vs UITextView: the compatibility gap Apple left behind</title>
      <dc:creator>nooraja</dc:creator>
      <pubDate>Thu, 27 Aug 2026 06:25:23 +0000</pubDate>
      <link>https://dev.to/nooraja/swiftui-text-selection-vs-uitextview-the-compatibility-gap-apple-left-behind-15fe</link>
      <guid>https://dev.to/nooraja/swiftui-text-selection-vs-uitextview-the-compatibility-gap-apple-left-behind-15fe</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffmlxx05wifco5kq5tdla.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffmlxx05wifco5kq5tdla.png" alt="Illustration comparing whole-value and range text selection" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;SwiftUI makes enabling it delightfully small:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kt"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Request failed: 401 Unauthorized"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;textSelection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h2&gt;
  
  
  The short version
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Platform / version&lt;/th&gt;
&lt;th&gt;What &lt;code&gt;.textSelection(.enabled)&lt;/code&gt; does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;macOS 12+&lt;/td&gt;
&lt;td&gt;Lets people select a text range with normal pointer and keyboard conventions.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;iOS / iPadOS 15 through 26&lt;/td&gt;
&lt;td&gt;Long press exposes a system menu that acts on the whole &lt;code&gt;Text&lt;/code&gt; view. No native range handles for selecting only part of that &lt;code&gt;Text&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;iOS / iPadOS 27+&lt;/td&gt;
&lt;td&gt;Long press shows selection highlight and handles, enabling range selection.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What SwiftUI gets right
&lt;/h2&gt;

&lt;h3&gt;
  
  
  One clear, composable API
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kt"&gt;VStack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;alignment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;leading&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Build failed"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;font&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headline&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kt"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logOutput&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;font&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;system&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;design&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;monospaced&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;textSelection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h3&gt;
  
  
  Great for whole-value copying
&lt;/h3&gt;

&lt;p&gt;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 &lt;em&gt;displayed safely&lt;/em&gt;, 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.&lt;/p&gt;

&lt;h3&gt;
  
  
  Native accessibility and platform conventions
&lt;/h3&gt;

&lt;p&gt;SwiftUI hands the interaction to the system, which is generally a good default. On macOS in particular, &lt;code&gt;.textSelection(.enabled)&lt;/code&gt; feels natural immediately.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where SwiftUI &lt;code&gt;Text&lt;/code&gt; falls short
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Partial selection on iOS was missing until iOS 27
&lt;/h3&gt;

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

&lt;p&gt;This is the central limitation. The modifier can make an app &lt;em&gt;look&lt;/em&gt; like it supports selection while delivering a less capable experience on most deployed versions.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. No selection inside &lt;code&gt;Button&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Apple documents that &lt;code&gt;Button&lt;/code&gt; 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 &lt;code&gt;.textSelection&lt;/code&gt; to solve both jobs.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Limited control over the interaction
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Text&lt;/code&gt; 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  SwiftUI &lt;code&gt;Text&lt;/code&gt; versus UIKit &lt;code&gt;UITextView&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The useful UIKit comparison is &lt;code&gt;UITextView&lt;/code&gt;, not &lt;code&gt;UILabel&lt;/code&gt;.&lt;/p&gt;

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

&lt;p&gt;Unlike &lt;code&gt;Text&lt;/code&gt;, it is imperative and heavier. In SwiftUI, it needs a &lt;code&gt;UIViewRepresentable&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;UILabel&lt;/code&gt; 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.&lt;/p&gt;

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

&lt;p&gt;So the relevant decision is &lt;strong&gt;&lt;code&gt;Text&lt;/code&gt; vs a read-only &lt;code&gt;UITextView&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The UIKit fallback for older iOS versions
&lt;/h2&gt;

&lt;p&gt;When range selection is a requirement before iOS 27, bridge a non-editable &lt;code&gt;UITextView&lt;/code&gt; into SwiftUI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;SwiftUI&lt;/span&gt;
&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;UIKit&lt;/span&gt;

&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;SelectableTextView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UIViewRepresentable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;attributedText&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;NSAttributedString&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;makeUIView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;UITextView&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;textView&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;UITextView&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;textView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isEditable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
        &lt;span class="n"&gt;textView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isSelectable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="n"&gt;textView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isScrollEnabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
        &lt;span class="n"&gt;textView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;backgroundColor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clear&lt;/span&gt;
        &lt;span class="n"&gt;textView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;textContainerInset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zero&lt;/span&gt;
        &lt;span class="n"&gt;textView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;textContainer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lineFragmentPadding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="n"&gt;textView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;adjustsFontForContentSizeCategory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;textView&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;updateUIView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;textView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UITextView&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;textView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attributedText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;attributedText&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Usage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kt"&gt;SelectableTextView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;attributedText&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;NSAttributedString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nv"&gt;string&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;longReleaseNote&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nv"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;font&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UIFont&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preferredFont&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;forTextStyle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;foregroundColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UIColor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;UITextView&lt;/code&gt; has more layout and accessibility details to verify than &lt;code&gt;Text&lt;/code&gt;: Dynamic Type, text-container sizing, links, scrolling, &lt;code&gt;ScrollView&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;For custom UIKit text components, &lt;code&gt;UITextInteraction&lt;/code&gt; 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  A pragmatic compatibility strategy
&lt;/h2&gt;

&lt;p&gt;Choose the implementation based on the user task, not ideology:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;#available&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;iOS&lt;/span&gt; &lt;span class="mf"&gt;27.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;articleBody&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;textSelection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;SelectableTextView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nv"&gt;attributedText&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;NSAttributedString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;string&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;articleBody&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h3&gt;
  
  
  Decision guide
&lt;/h3&gt;

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

&lt;h2&gt;
  
  
  Final thought
&lt;/h2&gt;

&lt;p&gt;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, &lt;code&gt;.textSelection(.enabled)&lt;/code&gt; on iPhone is closer to “make this whole value copyable” than “let the user select text.”&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/documentation/swiftui/view/textselection%28_%3A%29" rel="noopener noreferrer"&gt;Apple Developer Documentation — &lt;code&gt;textSelection(_:)&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/design/human-interface-guidelines/labels" rel="noopener noreferrer"&gt;Apple Human Interface Guidelines — Labels&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/documentation/uikit/uitextview" rel="noopener noreferrer"&gt;Apple Developer Documentation — &lt;code&gt;UITextView&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/documentation/uikit/adopting-system-selection-ui-in-custom-text-views" rel="noopener noreferrer"&gt;Apple Developer Documentation — Adopting system selection UI in custom text views&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>swiftui</category>
      <category>ios</category>
      <category>swift</category>
      <category>uikit</category>
    </item>
  </channel>
</rss>
