
Using rich text in the TextEditor with SwiftUI
Explore the usage of rich text within the TextEditor in SwiftUI using AttributedString.
When you need to display and edit long-form text in SwiftUI, TextEditor
is often the natural choice. By default, it works with a simple String
and behaves as a plain text editor, making it ideal for simple notes or basic input fields.
But starting with iOS 26, macOS 26, and related platforms, TextEditor
gained first-class support for AttributedString
. With this change, you can transition from editing plain text to creating fully formatted rich text, complete with Markdown, links, attribute transformations, and more.
Plain Text Editing
The most basic use case of TextEditor
involves binding it to a String
.
struct PlainTextEditor: View {
@State private var plainText = "This is plain text"
var body: some View {
TextEditor(text: $plainText)
}
}
This editor is straightforward: it captures user input as unformatted text. If you only need to store or process raw strings, this is often sufficient.
Enabling Rich Text with AttributedString
To enable formatting, change the type of the variable bound to the text editor to AttributedString
.
struct RichTextEditor: View {
@State private var richText: AttributedString = "This is rich text"
var body: some View {
TextEditor(text: $richText)
}
}
With this small change, the editor automatically supports styling. Users can add bold, italic and underline style directly inside the editor, and your app captures these attributes as part of the AttributedString
.
Creating and combining Attributed Strings
AttributedString
is not limited to editing. You can also build rich text programmatically by combining multiple instances.
Append:
var a = AttributedString("Hello, ")
var b = AttributedString("world")
b.font = .body.bold()
a.append(b)
Operator +=
:
a += AttributedString("!")
Insert:
if let index = a.firstIndex(of: ",") {
a.insert(AttributedString(" dear"), at: a.index(after: index))
}
These operations preserve attributes correctly, making it easy to construct styled text dynamically.
Creating Rich Content with Markdown
AttributedString
has built-in Markdown support as well. This means you can easily create rich text from Markdown content, whether it comes from user input or a server response.
do {
let thankYou = try AttributedString(
markdown: "**Thank you!** Visit our [website](<https://www.createwithswift.com>)"
)
print(thankYou)
} catch {
print("Markdown parsing failed: \(error.localizedDescription)")
}
Markdown parsing respects attributes like bold, italic, links, and even inline code formatting.
Selection in Rich Text
Working with selections allows you to inspect or transform only part of the text.
struct RichSelectTextEditor: View {
@State private var text: AttributedString = "This is rich text"
@State private var selection = AttributedTextSelection()
var body: some View {
TextEditor(text: $text, selection: $selection)
}
}
With AttributedTextSelection
, you gain access not only to the text ranges but also to the attributes applied within those ranges.
Inspecting and Modifying Attributes
Selections don’t just give you ranges of text. They also provide typingAttributes
: the attributes that will be applied to newly inserted text at the current cursor position.
let typingAttributes = selection.typingAttributes(in: text)
With typing attributes, you can synchronize your UI with the editor state. For example, you might enable or disable a “Bold” toggle depending on whether the cursor is currently inside bold text.
If you want to apply formatting changes instead, use this method:
attributedText.transformAttributes(in: &selection) { attributes in
...
}
This method works in three important ways:
&selection
as inout parameter
The selection is passed with&
because SwiftUI may need to adjust it while attributes are applied. For example, if adjacent runs merge after editing, the selection stays valid.attributes
as aninout AttributeContainer
Inside the closure, you receive anAttributeContainer
, which represents a dictionary of text attributes. Mutating it updates the attributes for the entire selection.- Safe and consistent updates
Unlike directly modifying individual ranges,transformAttributes
ensures that attribute runs are merged when possible, keeping your attributed text normalized and avoiding unnecessary fragmentation.
There are many different attributes you can safely transform with this method:
attributedText.transformAttributes(in: &selection) { attributes in
// Font styling
attributes.font = (attributes.font ?? .default).bold(true)
attributes.font = (attributes.font ?? .default).italic(true)
attributes.font = .system(size: 18, weight: .semibold, design: .rounded)
// Underline and strikethrough
attributes.underlineStyle = .single
attributes.strikethroughStyle = .single
attributes.strikethroughColor = .red
// Colors
attributes.foregroundColor = .blue
attributes.backgroundColor = .yellow
// Links
attributes.link = URL(string: "https://www.createwithswift.com")
// Typography adjustments
attributes.baselineOffset = 2
attributes.kern = 1.5
}
By combining these attributes, you can build editing features that go well beyond bold and italic, everything from links to custom colors, typography adjustments and much more.
A Formatting Toolbar Example
Here’s how to connect a simple toggle to the editor’s current selection:
@Environment(\.fontResolutionContext) private var fontResolutionContext
Toggle(
"Toggle Bold",
systemImage: "bold",
isOn: Binding(
get: {
let font = attributedTextSelection.typingAttributes(in: attributedText).font
let resolved = (font ?? .default).resolve(
in: fontResolutionContext
)
return resolved.isBold
},
set: { isBold in
attributedText.transformAttributes(in: &attributedTextSelection) {
$0.font = ($0.font ?? .default).bold(isBold)
}
}
)
)
This pattern works for bold, italic, underline, color, and other attributes. With a few controls, you can build a functional text formatting toolbar.
The fontResolutionContext
environment value helps you resolve the typingAttributes
based on the user context. Apple made it an environment value because fonts in SwiftUI are adaptive resources, not absolute values. By resolving fonts in the environment, you ensure that formatting logic in your editor always matches what the user actually sees, including dynamic type, accessibility, and platform-specific typography.
Switching from String
to AttributedString
in TextEditor
opens up a complete set of rich text capabilities in SwiftUI. With built-in Markdown parsing, AttributedTextSelection
, attribute transformations, and composition methods such as append
and insert
, you can create editing experiences that go far beyond plain text.
These tools give you the foundation to implement anything from lightweight note editors to feature-rich text fields that rival dedicated word processors, all natively in SwiftUI.