Styled Text Editing
Patterns for displaying styled text and building rich text editors in SwiftUI. Covers Text styling, AttributedString, TextEditor with selection-based formatting, and custom formatting definitions.
When This Skill Activates
Use this skill when the user:
- Wants to display styled or formatted text
- Needs rich text editing with bold/italic/underline controls
- Asks about AttributedString or TextEditor
- Wants text formatting toolbars
- Asks about Markdown rendering in Text views
- Needs custom text attributes or formatting constraints
- Wants selection-based text formatting
Decision Tree
What text feature do you need?
|
+- Display styled read-only text
| +- Simple styling (one style) -> Text Styling section below
| +- Mixed styles in one string -> AttributedString section below
| +- Markdown content -> Markdown section below
|
+- Edit plain text
| +- TextEditor(text: $stringBinding)
|
+- Edit rich/styled text
| +- Basic rich editor -> Rich Text Editing section below
| +- With formatting toolbar -> Formatting Controls section below
| +- With custom constraints -> Custom Formatting section below
API Availability
| API | Minimum Version | Notes |
|-----|----------------|-------|
| Text with modifiers | iOS 13 | .font(), .bold(), .italic() |
| TextEditor(text:) | iOS 14 | Plain string binding |
| .foregroundStyle() | iOS 15 | Preferred over .foregroundColor() |
| AttributedString | iOS 15 | Rich text model |
| .bold(condition) | iOS 16 | Conditional bold/italic |
| .underline(pattern:) | iOS 16 | Dash, dot patterns |
| TextEditor(text:selection:) | iOS 18 | AttributedString + selection |
| AttributedTextSelection | iOS 18 | Selection-based formatting |
| text.transformAttributes(in:) | iOS 18 | Modify attributes in selection |
| AttributedTextFormattingDefinition | iOS 18 | Custom formatting constraints |
| @Environment(\.fontResolutionContext) | iOS 18 | Resolve font properties |
Top 5 Mistakes
| # | Mistake | Fix |
|---|---------|-----|
| 1 | Using .foregroundColor() on new projects | Use .foregroundStyle() — supports gradients and ShapeStyle |
| 2 | .animation(.spring()) without value: on text | Deprecated in iOS 15 — always pass value: parameter |
| 3 | Expecting full Markdown in Text | Text only supports inline Markdown: bold, italic, [links]. No lists, tables, code blocks, images |
| 4 | Creating new AttributedString instances on every body evaluation | Cache complex AttributedString objects in @State or computed properties outside body |
| 5 | Using TextEditor(text: $string) for rich text | Use TextEditor(text: $attributedString, selection: $selection) with AttributedString binding (iOS 18+) |
Text Styling Quick Reference
// Font
Text("Hello").font(.headline)
Text("Custom").font(.system(size: 24, design: .rounded))
// Weight
Text("Bold").fontWeight(.bold)
// Color — prefer foregroundStyle over foregroundColor
Text("Styled").foregroundStyle(.red)
Text("Gradient").foregroundStyle(
.linearGradient(colors: [.yellow, .blue], startPoint: .top, endPoint: .bottom)
)
// Decorations
Text("Bold").bold()
Text("Conditional").bold(someCondition)
Text("Underline").underline(true, pattern: .dash, color: .blue)
Text("Strike").strikethrough(true, pattern: .dot, color: .red)
// Layout
Text("Centered").multilineTextAlignment(.center)
Text("Spaced").lineSpacing(10)
Text("Truncated").lineLimit(2).truncationMode(.tail)
AttributedString
// Create and style ranges
var text = AttributedString("Red and Blue")
if let redRange = text.range(of: "Red") {
text[redRange].foregroundColor = .red
text[redRange].font = .headline
}
if let blueRange = text.range(of: "Blue") {
text[blueRange].foregroundColor = .blue
text[blueRange].underlineStyle = .single
}
// Display
Text(text)
Available Attributes
| Attribute | Type | Example |
|-----------|------|---------|
| .font | Font | .headline |
| .foregroundColor | Color | .red |
| .backgroundColor | Color | .yellow |
| .underlineStyle | UnderlineStyle | .single |
| .underlineColor | Color | .blue |
| .strikethroughStyle | UnderlineStyle | .single |
| .strikethroughColor | Color | .red |
| .inlinePresentationIntent | InlinePresentationIntent | .stronglyEmphasized (bold), .emphasized (italic) |
Rich Text Editing (iOS 18+)
struct RichTextEditorView: View {
@State private var text = AttributedString("Select text to format")
@State private var selection = AttributedTextSelection()
@Environment(\.fontResolutionContext) private var fontResolutionContext
var body: some View {
VStack {
TextEditor(text: $text, selection: $selection)
.frame(height: 200)
HStack {
Button(action: toggleBold) {
Image(systemName: "bold")
}
Button(action: toggleItalic) {
Image(systemName: "italic")
}
Button(action: toggleUnderline) {
Image(systemName: "underline")
}
}
}
}
private func toggleBold() {
text.transformAttributes(in: &selection) {
let font = $0.font ?? .default
let resolved = font.resolve(in: fontResolutionContext)
$0.font = font.bold(!resolved.isBold)
}
}
private func toggleItalic() {
text.transformAttributes(in: &selection) {
let font = $0.font ?? .default
let resolved = font.resolve(in: fontResolutionContext)
$0.font = font.italic(!resolved.isItalic)
}
}
private func toggleUnderline() {
text.transformAttributes(in: &selection) {
if $0.underlineStyle != nil {
$0.underlineStyle = nil
} else {
$0.underlineStyle = .single
}
}
}
}
Reading Selection Attributes
// Get current attributes at the selection/cursor
let attributes = selection.typingAttributes(in: text)
let currentColor = attributes.foregroundColor ?? .primary
// Set color on selection
text.transformAttributes(in: &selection) {
$0.foregroundColor = .blue
}
Custom Formatting Definition (iOS 18+)
Constrain which formatting options are available in the editor:
struct MyTextFormatting: AttributedTextFormattingDefinition {
typealias Scope = AttributeScopes.SwiftUIAttributes
static let fontWeight = ValueConstraint(
\.font,
constraint: { font in
guard let font = font else { return nil }
let weight = font.resolve().weight
return font.weight(weight == .bold ? .regular : .bold)
}
)
}
// Apply to TextEditor
TextEditor(text: $text, selection: $selection)
.textFormattingDefinition(MyTextFormatting.self)
Markdown in Text
// Inline markdown support
Text("This is **bold** and *italic* text")
Text("Visit [Apple](https://www.apple.com)")
Text("Code: `inline code`")
Markdown Limitations
Text supports only inline Markdown:
- ✅ Bold (
**text**), italic (*text*), links, inline code - ❌ Line breaks, block formatting, lists, tables, code blocks, images, block quotes
Review Checklist
Text Display
- [ ] Using
.foregroundStyle()instead of deprecated.foregroundColor() - [ ]
.lineLimit()paired with.help()tooltip or.textSelection(.enabled)for truncated text - [ ] Dynamic Type supported — using system font styles, not hardcoded sizes
- [ ] Colors adapt to dark mode — using system colors or semantic styles
AttributedString
- [ ] Complex AttributedString cached in @State, not recreated in body
- [ ] Range lookups use safe
if let range = text.range(of:)pattern - [ ] Appropriate attributes used (
.inlinePresentationIntentfor semantic bold/italic)
TextEditor
- [ ] Rich text uses
TextEditor(text: $attributedString, selection: $selection)(iOS 18+) - [ ] Formatting toolbar uses
text.transformAttributes(in: &selection)pattern - [ ] Font resolution uses
@Environment(\.fontResolutionContext)for toggle logic - [ ] Accessibility labels provided for formatting buttons
Localization
- [ ] User-facing strings use
LocalizedStringKey - [ ] Markdown in localized strings uses
Text(LocalizedStringKey("**Bold** text"))