- name
- attributed-string
- description
- AttributedString patterns for rich text formatting, alignment, selection, and SwiftUI integration. Use when working with styled text, text editing, or AttributedString APIs.
- allowed-tools
- ["Read","Glob","Grep"]
- last_verified
- 2026-07-16T00:00:00.000Z
- review_by
- 2027-06-22T00:00:00.000Z
- os_version
- iOS 27 / macOS 27
# AttributedString Patterns
Correct API shapes and patterns for Foundation's `AttributedString`. Covers creating styled text, applying attributes to ranges, text alignment, writing direction, line height control, text selection and editing, discontiguous substrings, and SwiftUI integration.
## When This Skill Activates
Use this skill when the user:
- Asks about **AttributedString** creation or manipulation
- Wants to **style text** with fonts, colors, underlines, or other attributes
- Mentions **text alignment**, **writing direction**, or **line height**
- Asks about **text selection** or **text editing** with AttributedString
- Wants to work with **DiscontiguousAttributedSubstring** or **RangeSet**
- Mentions **TextEditor** with **AttributedString** in SwiftUI
- Asks about **rich text** formatting in Swift
- Wants to **replace** or **modify** text within an AttributedString
- Mentions **paragraphStyle**, **textSelectionAffinity**, or **AttributedTextSelection**
## Decision Tree
```
What 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 Availability
| API | Minimum Version | Notes |
|-----|----------------|-------|
| `AttributedString` | iOS 15 / macOS 12 | Swift-native replacement for NSAttributedString |
| `AttributedString.paragraphStyle` | iOS 15 / macOS 12 | Uses NSMutableParagraphStyle |
| `AttributedString.writingDirection` | iOS 26 / macOS 26 | New in 2025 |
| `AttributedString.LineHeight` | iOS 26 / macOS 26 | `.exact(points:)`, `.multiple(factor:)`, `.loose` |
| `AttributedString.alignment` | iOS 26 / macOS 26 | `.left`, `.center`, `.right` |
| `AttributedTextSelection` | iOS 26 / macOS 26 | Programmatic text selection |
| `replaceSelection(_:withCharacters:)` | iOS 26 / macOS 26 | Replace selection with plain characters |
| `replaceSelection(_:with:)` | iOS 26 / macOS 26 | Replace selection with AttributedString |
| `DiscontiguousAttributedSubstring` | iOS 26 / macOS 26 | Non-contiguous range selections |
| `AttributedString.utf8` | iOS 26 / macOS 26 | UTF-8 code unit view |
| `TextEditor(text:selection:)` with AttributedString | iOS 26 / macOS 26 | SwiftUI rich text editing |
| `.textSelectionAffinity(_:)` | iOS 26 / macOS 26 | Control cursor affinity at line boundaries |
## Top 5 Mistakes
| # | Mistake | Fix |
|---|---------|-----|
| 1 | Using `NSAttributedString` in new Swift code | Use `AttributedString` (iOS 15+) for type-safe, Swift-native attributes |
| 2 | Applying range-based attributes without checking the range exists | Always safely unwrap the result of `text.range(of:)` before subscripting |
| 3 | Forgetting that `AttributedString` is a value type | Mutations require `var`, not `let`; assign attributes after declaring as `var` |
| 4 | Building `NSMutableParagraphStyle` when new alignment API is available | Use `text.alignment = .center` on iOS 26+ instead of manual paragraph styles |
| 5 | Modifying the original string instead of the selection when using `replaceSelection` | Pass the selection as `inout` and let the API update the selection range for you |
## Creating and Styling
### Basic Initialization
```swift
// Plain text
let plain = AttributedString("Hello, world!")
// With attributes applied inline
var bold = AttributedString("Bold text")
bold.font = .boldSystemFont(ofSize: 16)
```
### Applying Attributes to Ranges
```swift
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
}
```
### Creating from a Substring
```swift
let source = AttributedString("Hello, world!")
if let range = source.range(of: "world") {
let substring = source[range]
let extracted = AttributedString(substring) // standalone copy
}
```
| Pattern | Verdict |
|---------|---------|
| `var text = AttributedString("...")` then mutate | Correct |
| `let text = AttributedString("...")` then mutate | Will not compile -- value type requires `var` |
| Force-unwrapping `text.range(of:)!` | Fragile -- use `if let` or `guard let` |
## Text Alignment and Formatting
### Legacy Approach (iOS 15+)
```swift
var paragraph = AttributedString("Centered paragraph of text")
let style = NSMutableParagraphStyle()
style.alignment = .center
paragraph.paragraphStyle = style
```
### Modern Approach (iOS 26+)
```swift
var paragraph = AttributedString("Centered paragraph of text")
paragraph.alignment = .center
```
Available `TextAlignment` values:
| Value | Description |
|-------|-------------|
| `.left` | Left-aligned text |
| `.right` | Right-aligned text |
| `.center` | Center-aligned text |
## Writing Direction and Line Height
### Writing Direction (iOS 26+)
```swift
var text = AttributedString("Hello عربي")
text.writingDirection = .rightToLeft
```
| Value | Description |
|-------|-------------|
| `.leftToRight` | Standard LTR layout |
| `.rightToLeft` | RTL layout for Arabic, Hebrew, etc. |
### Line Height (iOS 26+)
```swift
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 |
|------|----------|
| `.exact(points:)` | Pixel-perfect designs with fixed line heights |
| `.multiple(factor:)` | Proportional scaling relative to font size |
| `.loose` | Comfortable reading spacing chosen by the system |
## Text Selection and Editing
### Replacing Selected Text (iOS 26+)
```swift
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)
```
Key point: use `withCharacters:` for plain text, `with:` for styled replacements.
## DiscontiguousAttributedSubstring
Select and manipulate multiple non-contiguous ranges at once (iOS 26+).
```swift
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)
}
```
## UTF-8 View
Access raw UTF-8 code units (iOS 26+):
```swift
let text = AttributedString("Hello")
for codeUnit in text.utf8 {
print(codeUnit)
}
```
## SwiftUI Integration
### TextEditor with AttributedString and Selection (iOS 26+)
```swift
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)
)
)
}
}
}
```
### Text Selection Affinity
Control which line the cursor appears on when positioned at a line boundary:
```swift
TextEditor(text: $text, selection: $selection)
.textSelectionAffinity(.upstream)
```
`.upstream` keeps the cursor at the end of the previous line; `.downstream` moves it to the start of the next.
### Displaying Styled Text in SwiftUI
```swift
// Simple display
Text(attributedString)
// With text selection enabled
Text(attributedString)
.textSelection(.enabled)
```
## Review Checklist
### Correctness
- [ ] Using `AttributedString` (not `NSAttributedString`) for new Swift code
- [ ] All range lookups safely unwrapped with `if let` or `guard let`
- [ ] String declared as `var` before applying attributes
- [ ] `replaceSelection` passes selection as `inout` (`&selection`)
### Platform and Versioning
- [ ] New APIs (alignment, writingDirection, lineHeight, selection) gated to iOS 26+ / macOS 26+
- [ ] Legacy paragraph style approach used for iOS 15-25 targets
- [ ] Availability checks (`if #available`) wrap newer APIs when supporting older deployment targets
### SwiftUI
- [ ] `TextEditor(text:selection:)` overload used for rich text editing (iOS 26+)
- [ ] `.textSelectionAffinity` applied where cursor behavior at line wraps matters
- [ ] `.textSelection(.enabled)` added to `Text` views that display user content
### Accessibility
- [ ] Foreground and background color combinations meet contrast requirements
- [ ] Styled text does not rely solely on color to convey meaning (add underline, bold, or icons)
- [ ] Writing direction set correctly for RTL language content
## References
- [AttributedString | Apple Developer Documentation](https://developer.apple.com/documentation/foundation/attributedstring)
- [Text | Apple Developer Documentation](https://developer.apple.com/documentation/swiftui/text)
- [TextEditor | Apple Developer Documentation](https://developer.apple.com/documentation/swiftui/texteditor)
Auf GitHub ansehen