How it works
AttributedTextSelection represents the current selection range within editable attributed text, tracking which characters the user has highlighted in a TextEditor or text field bound to an AttributedString. Because attributed text carries styling at the run level, a plain character offset isn't enough to address a selection safely as the text mutates; AttributedTextSelection models the selection in terms that stay valid against the underlying AttributedString. Reach for it when you want to read or act on whatever the user has selected — for example, to apply formatting to just the highlighted portion of rich text.
Hold the selection in @State
AttributedTextSelection is a value type you own alongside the text itself, so you store it in state and let the editor mutate it. Here
@State private var selection = AttributedTextSelection()starts as an empty selection and updates as the user clicks and drags.Bind it to the editor with selection:
An editable text view that works on rich text accepts a selection binding next to its text binding, keeping AttributedTextSelection in sync with what's highlighted on screen. The example wires both together as
TextEditor(text: $text, selection: $selection), so every drag updatesselectionlive.Resolve the selected ranges with indices(in:)
To act on the selection you ask it for concrete positions in a given AttributedString via
indices(in:), which returns a value you can pattern-match. The code unwraps it withif case let .ranges(ranges) = selection.indices(in: text), yielding the set of character ranges the user picked.Mutate styling through transform(updating:)
Editing the AttributedString can shift indices, so changes flow through
transform(updating:), which keeps the selection consistent as runs are rewritten. Inside the closure the example walksranges.rangesand setsruns[range].font = .body.bold(), bolding exactly the selected characters while AttributedTextSelection is migrated forward.
transform(updating:) closure, change runs[range].font = .body.bold() to runs[range].foregroundColor = .red and watch the same selection drive a color change instead of a weight change.Example & preview
Press Run live & edit to compile it in your browser — then edit the Swift on the left and the preview re-renders live.
struct AttributedTextSelectionDemo: View {
@State private var text = AttributedString("Edit and select this text")
@State private var selection = AttributedTextSelection()
var body: some View {
VStack(alignment: .leading, spacing: 12) {
TextEditor(text: $text, selection: $selection)
.frame(height: 120)
.border(.secondary)
Button("Bold Selection") {
text.transform(updating: &selection) { runs in
if case let .ranges(ranges) = selection.indices(in: text) {
for range in ranges.ranges {
runs[range].font = .body.bold()
}
}
}
}
.buttonStyle(.borderedProminent)
}
.padding()
}
}