Loading...
Loading...
AttributedString patterns for rich text formatting, alignment, selection, and SwiftUI integration. Use when working with styled text, text editing, or AttributedString APIs.
npx skill4agent add rshankras/claude-code-apple-skills attributed-stringAttributedStringWhat do you need with AttributedString?
|
+-- Create or style text
| |
| +-- Simple inline attributes (font, color)
| | --> Creating and Styling section
| |
| +-- Paragraph-level formatting (alignment, line height)
| --> Text Alignment and Formatting section
|
+-- Control text layout
| |
| +-- Writing direction (LTR / RTL)
| | --> Writing Direction and Line Height section
| |
| +-- Line spacing / height
| --> Writing Direction and Line Height section
|
+-- Edit or select text programmatically
| |
| +-- Replace selection with characters or AttributedString
| | --> Text Selection and Editing section
| |
| +-- Work with multiple non-contiguous ranges
| --> DiscontiguousAttributedSubstring section
|
+-- Display in SwiftUI
--> SwiftUI Integration section| API | Minimum Version | Notes |
|---|---|---|
| iOS 15 / macOS 12 | Swift-native replacement for NSAttributedString |
| iOS 15 / macOS 12 | Inline attribute |
| iOS 15 / macOS 12 | Inline attribute |
| iOS 15 / macOS 12 | Uses NSMutableParagraphStyle |
| iOS 26 / macOS 26 | New in 2025 |
| iOS 26 / macOS 26 | |
| iOS 26 / macOS 26 | |
| iOS 26 / macOS 26 | Programmatic text selection |
| iOS 26 / macOS 26 | Replace selection with plain characters |
| iOS 26 / macOS 26 | Replace selection with AttributedString |
| iOS 26 / macOS 26 | Non-contiguous range selections |
| iOS 26 / macOS 26 | UTF-8 code unit view |
| iOS 26 / macOS 26 | SwiftUI rich text editing |
| iOS 26 / macOS 26 | Control cursor affinity at line boundaries |
| # | Mistake | Fix |
|---|---|---|
| 1 | Using | Use |
| 2 | Applying range-based attributes without checking the range exists | Always safely unwrap the result of |
| 3 | Forgetting that | Mutations require |
| 4 | Building | Use |
| 5 | Modifying the original string instead of the selection when using | Pass the selection as |
// Plain text
let plain = AttributedString("Hello, world!")
// With attributes applied inline
var bold = AttributedString("Bold text")
bold.font = .boldSystemFont(ofSize: 16)var text = AttributedString("Styled text")
text.foregroundColor = .red
text.backgroundColor = .yellow
text.font = .systemFont(ofSize: 14)
// Attribute on a specific range
if let range = text.range(of: "Styled") {
text[range].underlineStyle = .single
text[range].underlineColor = .blue
}let source = AttributedString("Hello, world!")
if let range = source.range(of: "world") {
let substring = source[range]
let extracted = AttributedString(substring) // standalone copy
}| Pattern | Verdict |
|---|---|
| Correct |
| Will not compile -- value type requires |
Force-unwrapping | Fragile -- use |
var paragraph = AttributedString("Centered paragraph of text")
let style = NSMutableParagraphStyle()
style.alignment = .center
paragraph.paragraphStyle = stylevar paragraph = AttributedString("Centered paragraph of text")
paragraph.alignment = .centerTextAlignment| Value | Description |
|---|---|
| Left-aligned text |
| Right-aligned text |
| Center-aligned text |
var text = AttributedString("Hello عربي")
text.writingDirection = .rightToLeft| Value | Description |
|---|---|
| Standard LTR layout |
| RTL layout for Arabic, Hebrew, etc. |
var multiline = AttributedString(
"This is a paragraph\nwith multiple lines\nof text."
)
// Exact point value
multiline.lineHeight = .exact(points: 32)
// Multiplier of the default line height
multiline.lineHeight = .multiple(factor: 2.5)
// System-defined loose spacing
multiline.lineHeight = .loose| Mode | Use Case |
|---|---|
| Pixel-perfect designs with fixed line heights |
| Proportional scaling relative to font size |
| Comfortable reading spacing chosen by the system |
var text = AttributedString("Here is my dog")
var selection = AttributedTextSelection(range: text.range(of: "dog")!)
// Replace with plain characters
text.replaceSelection(&selection, withCharacters: "cat")
// Replace with an AttributedString
let replacement = AttributedString("horse")
text.replaceSelection(&selection, with: replacement)selectioninoutwithCharacters:with:let text = AttributedString("Select multiple parts of this text")
if let range1 = text.range(of: "Select"),
let range2 = text.range(of: "text") {
let rangeSet = RangeSet([range1, range2])
var substring = text[rangeSet] // DiscontiguousAttributedSubstring
substring.backgroundColor = .yellow
// Flatten into a single contiguous AttributedString
let combined = AttributedString(substring)
}| Operation | Result Type |
|---|---|
| |
| |
| Flattened |
let text = AttributedString("Hello")
for codeUnit in text.utf8 {
print(codeUnit)
}struct SuggestionTextEditor: View {
@State var text: AttributedString = ""
@State var selection = AttributedTextSelection()
var body: some View {
VStack {
TextEditor(text: $text, selection: $selection)
SuggestionsView(
substrings: getSubstrings(
text: text,
indices: selection.indices(in: text)
)
)
}
}
}TextEditor(text: $text, selection: $selection)
.textSelectionAffinity(.upstream)| Value | Behavior |
|---|---|
| Cursor stays at end of previous line |
| Cursor moves to start of next line |
// Simple display
Text(attributedString)
// With text selection enabled
Text(attributedString)
.textSelection(.enabled)AttributedStringNSAttributedStringif letguard letvarreplaceSelectioninout&selectionif #availableTextEditor(text:selection:).textSelectionAffinity.textSelection(.enabled)Text