I completely understand that feeling: you’re testing an app, you see a tracking number, an address, or a super long discount code, you tap and hold on the screen hoping to copy it and… nothing happens. As users, it’s frustrating. As creators, it’s a user experience (UX) detail that we cannot overlook.
If you are an iOS Developer, you know that in the early days of creating declarative interfaces with Apple, allowing the user to copy static text required somewhat strange workarounds, like using a disabled UITextView or wrapping UIKit components. Fortunately, Swift programming evolves rapidly. Today we are going to dive into one of the simplest but most powerful tools at our disposal: Text Selection in SwiftUI.
In this comprehensive tutorial, you will learn what it is, how to implement it, and how to master it in your Swift projects, creating universal applications for iOS, macOS, and watchOS using Xcode.
The Importance of Text Selection in Swift programming
In the modern Apple development ecosystem, SwiftUI has changed the game. It allows us to write code once and deploy it across multiple platforms. However, each platform has its own user interface conventions. On iOS, users expect to be able to long-press to select text; on macOS, they expect to click and drag the cursor; on watchOS, interactions are much more limited due to screen size.
Implementing Text Selection in SwiftUI correctly ensures that your app feels native on all these devices, elevating your profile as an iOS Developer and drastically improving user retention and satisfaction.
What is Text Selection in SwiftUI?
Introduced in iOS 15, macOS 12, and watchOS 8, the .textSelection modifier is a declarative API that allows developers to tell the system if the text within a view can be selected by the user.
Instead of having to resort to complex or default interactive components (like TextField or TextEditor), this modifier is applied directly to static views like Text. This means you can display read-only information (such as a blog article, a purchase receipt, or an error log) and allow the user to interact with the text natively to copy it, look it up in the dictionary, or share it, all with a single line of code in Swift.
Setting Up the Environment in Xcode
Before we start writing lines of code, let’s set up our workspace. For this tutorial, we will simulate that we are building “SnippetSaver”, a cross-platform application to save code snippets and technical notes.
- Open Xcode.
- Select Create a new Xcode project.
- Under the Multiplatform tab, choose App and click Next.
- Name your project (for example,
SnippetSaver). - Make sure the interface is set to SwiftUI and the language to Swift.
- Save the project in your preferred directory.
By choosing a cross-platform template, Xcode automatically configures the targets for iOS and macOS (and you can easily add watchOS). This is ideal for seeing how Text Selection in SwiftUI behaves in different environments simultaneously.
Basic Implementation: Selecting a Text View
Let’s start with the most basic use case. You have a simple Text view and you want the user to be able to copy its content.
Open your ContentView.swift file and modify the code so it looks like this:
import SwiftUI
struct ContentView: View {
var body: some View {
VStack(spacing: 20) {
Image(systemName: "doc.text.magnifyingglass")
.imageScale(.large)
.foregroundColor(.blue)
Text("You cannot copy this text.")
.font(.headline)
.foregroundColor(.secondary)
// Here we apply Text Selection in SwiftUI
Text("But you can copy this code snippet! let text = \"SwiftUI is awesome\"")
.font(.system(.body, design: .monospaced))
.padding()
.background(Color.gray.opacity(0.1))
.cornerRadius(8)
.textSelection(.enabled) // <- The magic happens here
}
.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Swift Code Analysis
Text("You cannot copy this text."): This text has the default behavior. If you run this in the iOS simulator or on your iPhone, no matter how long you press, the copy context menu will not appear..textSelection(.enabled): This is the crown jewel. By adding this modifier to the secondText, we are telling the SwiftUI rendering engine to enable the system’s native selection gestures.
If you test this in the iOS simulator (long press), you will see the blue selection handles and the pop-up menu (Copy, Share, etc.). If you change your target in Xcode to your Mac and run it, you will see that you can use the mouse to click and drag over the text to select it. This is Swift programming at its finest!
The Power of Inheritance in SwiftUI
As an iOS Developer, one of the things you will love most about SwiftUI is how it handles the Environment. The .textSelection modifier does not only apply to a single Text view, but it can propagate through the view hierarchy.
Imagine you have an “Invoice Details” screen with dozens of text fields. It would be very tedious and error-prone to add .textSelection(.enabled) to each of them. Instead, you can apply it to the parent container.
import SwiftUI
struct InvoiceDetailView: View {
var body: some View {
VStack(alignment: .leading, spacing: 15) {
Text("Invoice Details")
.font(.title)
.bold()
VStack(alignment: .leading, spacing: 5) {
Text("Client: Maria Garcia")
Text("Order No: #84759392")
Text("Date: October 24, 2023")
Text("Total: 154.50 €")
}
.padding()
.background(Color.blue.opacity(0.05))
.cornerRadius(10)
Text("Thank you for your purchase.")
.font(.footnote)
.foregroundColor(.gray)
}
.padding()
// We enable selection for the entire main VStack
.textSelection(.enabled)
}
}
What happens here?
By applying Text Selection in SwiftUI to the outermost VStack, all the Text elements contained within it inherit this property. Now the user can select the order number, the customer’s name, or the invoice total individually.
Granular Control: Enabling and Disabling Text Selection
Inheritance is fantastic, but sometimes you need exceptions. What if you want almost your entire view to be selectable, but there is specific text (like a hidden button or a legal disclaimer) that should not interact with this gesture so as not to confuse the user?
Swift programming allows us to override this behavior in specific child views using .textSelection(.disabled).
import SwiftUI
struct UserProfileView: View {
var body: some View {
Form {
Section(header: Text("Personal Information")) {
Text("User ID: UUID-9948-ABX")
Text("Email: developer@example.com")
}
Section(header: Text("System Settings")) {
Text("App Version: 2.1.0")
Text("Confidential Information. Do not copy.")
.foregroundColor(.red)
// We override the parent container's inheritance
.textSelection(.disabled)
}
}
// The whole form is selectable by default
.textSelection(.enabled)
}
}
In this example structured in Xcode, any information within the Form view can be copied by the user, with the exception of the red warning that has been explicitly disabled. This granularity is essential for building robust and professional interfaces.
Cross-Platform Considerations: iOS vs macOS vs watchOS
The true art of being a versatile iOS Developer is understanding that, although SwiftUI unifies the code, it does not unify (nor should it) the user experience, as each device has a different interaction paradigm.
On iOS and iPadOS
The implementation of Text Selection in SwiftUI translates to touch interactions. A long press invokes the magnifying glass and the selection handles (lollipops). It’s important to be careful if you place selectable text inside elements that already have their own gestures, like a Button or a NavigationLink. SwiftUI is smart and usually prioritizes the button’s gesture, which can make text selection difficult or impossible. If you need selectable text, place it outside the active touch areas for navigation.
On macOS
The Mac is the king of the mouse and keyboard. Here, .textSelection(.enabled) allows the classic click and drag to highlight text, as well as double-click to select a word and triple-click to select a paragraph. The cursor automatically changes to the text selection icon (“I-beam”) when you hover over it, providing excellent visual feedback, something native to Swift programming on macOS.
On watchOS
The Apple Watch presents a unique challenge. Its screens are tiny, and the main interaction paradigm is quick viewing and brief taps, not text selection or clipboard manipulation.
In current versions of watchOS, applying .textSelection(.enabled) generally does not have a deep interactive effect or is simply ignored by the system, as watchOS lacks a global clipboard accessible to the user in the same way as iOS.
However, thanks to the cross-platform nature of SwiftUI in Xcode, including this modifier in your shared code will not break your application on the Apple Watch. The system will gracefully omit the functionality, allowing you to maintain a single, clean codebase (Code Reuse).
Lists and Collections: A Common Challenge in Xcode
A common problem you will encounter as an iOS Developer is applying text selection inside a List. Because of how UIKit and AppKit internally handle list cells, applying selection globally can sometimes have erratic behaviors with swipe gestures (swipe to delete).
The best practice in Swift when working with collections is to apply the modifier directly to the row’s content, rather than to the entire list.
import SwiftUI
struct LogEvent: Identifiable {
let id = UUID()
let timestamp: String
let message: String
}
struct DebugConsoleView: View {
let logs = [
LogEvent(timestamp: "10:01:00", message: "App started successfully"),
LogEvent(timestamp: "10:01:05", message: "Error 404: Could not load network resource"),
LogEvent(timestamp: "10:02:12", message: "Connection retry successful")
]
var body: some View {
NavigationView {
List(logs) { log in
VStack(alignment: .leading) {
Text(log.timestamp)
.font(.caption)
.foregroundColor(.gray)
Text(log.message)
.font(.body)
}
// We apply the selection directly to the cell
.textSelection(.enabled)
}
.navigationTitle("Debug Console")
}
}
}
By doing it this way, you ensure that the user can copy that annoying “Error 404” message to send to you via support email, without interfering with the native navigation of the list.
Creating a Custom View Modifier
If you find yourself writing .textSelection(.enabled) constantly in your project in Xcode, you can embrace the philosophy of Swift programming and create your own semantic modifier. This not only cleans up your code but also makes it more readable.
import SwiftUI
// 1. We create the modifier
struct CopyableTextModifier: ViewModifier {
func body(content: Content) -> some View {
content
.textSelection(.enabled)
// We can add extra styles to indicate it is copyable
.contextMenu {
Button(action: {
// Optional extra action
print("Text interacted with")
}) {
Label("Interactive Text", systemImage: "info.circle")
}
}
}
}
// 2. We extend the view for cleaner usage
extension View {
func makeCopyable() -> some View {
self.modifier(CopyableTextModifier())
}
}
// 3. Usage in our interface
struct CustomModifierExample: View {
var body: some View {
Text("This is your transaction hash: 0x8F4B2...")
.makeCopyable() // Much more semantic!
.padding()
}
}
This is the mark of a true senior iOS Developer: adapting the framework’s tools to fit the narrative and architecture of your own code.
Accessibility and User Experience (UX)
We can never talk about interfaces in Xcode without mentioning accessibility (VoiceOver). Fortunately, SwiftUI does a phenomenal job of integrating Text Selection in SwiftUI with Apple’s accessibility services.
When text is selectable, VoiceOver allows the user to interact with it more deeply through the rotor, allowing them to copy snippets to the clipboard without needing to see the screen. As a creator, you don’t have to add extra code for this to work; simply using .textSelection(.enabled) immediately improves your app’s accessibility.
When NOT to use Text Selection
For the sake of candor and realism, it’s important to know when to stop. Not all text in your application should be selectable.
- Navigation labels: The titles of your
NavigationBaror the tabs of yourTabViewdo not need to be copyable. - Buttons: As mentioned, it interferes with the main action.
- Decorative texts: If the text is purely aesthetic and contains no useful information (like a watermark), disabling selection keeps the interface clean and prevents accidental gestural frustrations.
Conclusion
The introduction of Text Selection in SwiftUI is a clear testament to how Swift programming continues to mature, focusing on providing powerful tools to developers with incredibly concise syntax. What used to require dozens of lines of code in UIKit and complex extensions in Xcode is now resolved with a single, elegant line of code.