skills/swiftui/text-editing/SKILL.md
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.
npx skillsauth add rshankras/claude-code-apple-skills text-editingInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
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+) |
// 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)
// 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) |
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
}
}
}
}
// 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
}
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)
// 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"))development
Build, install, and launch an iOS app on a physical iPhone or iPad entirely from the command line (no Xcode GUI), using xcodebuild + devicectl. Use when the user wants to run, test, or screenshot their app on a real device without opening Xcode.
development
Comprehensive iOS development guidance including Swift best practices, SwiftUI patterns, UI/UX review against HIG, and app planning. Use for iOS code review, best practices, accessibility audits, or planning new iOS apps.
development
Build, install, launch, and screenshot an iOS app in the Simulator to verify a change visually. Use when the user wants to run the app, see a change live, screenshot the running app, or confirm a UI fix actually works (not just that it compiles).
development
Audits skills in this repo for consistency, API drift, and structural gaps. Produces a prioritized report grouped by severity (Critical/High/Medium/Low). Use when asked to "audit skills", "check the skill repo for drift", or when planning bulk skill cleanup. Read-only — does not apply fixes.