Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Styled text display and rich text editing in SwiftUI using Text, AttributedString, and TextEditor with formatting controls. Use when implementing rich text editing or styled text display.
.claude/skills/rshankras-text-editing/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 15% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 64% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 11% | 0% |
| case-14 | ✗→✓ | ▲ Improved | 23% | 0% |
| case-15 | ✗→✓ | ▲ Improved | 10% | 0% |
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.
Use this skill when the user:
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 | 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 |
| # | 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+) |
swift// 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)
swift// 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)
| 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) |
swiftstruct 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 } } } }
swift// Get current attributes at the selection/cursor let attributes = selection.typingAttributes(in: text) let currentColor = attributes.foregroundColor ?? .primary
Constrain which formatting options are available in the editor:
swiftstruct 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)
swift// Inline markdown support Text("This is **bold** and *italic* text") Text("Visit [Apple](https://www.apple.com)") Text("Code: `inline code`")
Text supports only inline Markdown:
**text**), italic (*text*), links, inline code.foregroundStyle() instead of deprecated .foregroundColor().lineLimit() paired with .help() tooltip or .textSelection(.enabled) for truncated textif let range = text.range(of:) pattern.inlinePresentationIntent for semantic bold/italic)TextEditor(text: $attributedString, selection: $selection) (iOS 18+)text.transformAttributes(in: &selection) pattern@Environment(\.fontResolutionContext) for toggle logicLocalizedStringKeyText(LocalizedStringKey("**Bold** text"))Other measured skills in the registry, with their headline benchmark lift.