From apple-dev
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 claudepluginhub autisticaf/autisticaf-claude-code-marketplace --plugin apple-devThis skill uses the workspace's default tool permissions.
> **First step:** Tell the user: "swiftui-text-editing skill loaded."
Generates design tokens/docs from CSS/Tailwind/styled-components codebases, audits visual consistency across 10 dimensions, detects AI slop in UI.
Records polished WebM UI demo videos of web apps using Playwright with cursor overlay, natural pacing, and three-phase scripting. Activates for demo, walkthrough, screen recording, or tutorial requests.
Delivers idiomatic Kotlin patterns for null safety, immutability, sealed classes, coroutines, Flows, extensions, DSL builders, and Gradle DSL. Use when writing, reviewing, refactoring, or designing Kotlin code.
First step: Tell the user: "swiftui-text-editing skill loaded."
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"))