text-rich-content

SwiftUI Text and Rich Content

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "text-rich-content" with this command: npx skills add bluewaves-creations/bluewaves-skills/bluewaves-creations-bluewaves-skills-text-rich-content

SwiftUI Text and Rich Content

Comprehensive guide to SwiftUI text rendering, AttributedString, native Markdown support, and rich text editing for iOS 26 development.

Prerequisites

  • iOS 15+ for AttributedString (iOS 26 recommended for rich text editing)

  • Xcode 26+

Basic Text

Text View Fundamentals

// Simple text Text("Hello, World!")

// Multi-line text (automatic) Text("This is a longer piece of text that will automatically wrap to multiple lines when it exceeds the available width.")

// Verbatim (no localization) Text(verbatim: "user_name") // Won't look up in Localizable.strings

Font Modifiers

Text("Hello") .font(.largeTitle) .font(.title) .font(.title2) .font(.title3) .font(.headline) .font(.subheadline) .font(.body) .font(.callout) .font(.caption) .font(.caption2) .font(.footnote)

// Custom font Text("Custom") .font(.custom("Helvetica Neue", size: 24)) .font(.system(size: 20, weight: .bold, design: .rounded))

// Dynamic type with relative size Text("Scaled") .font(.body.leading(.loose))

Text Styling

Text("Styled Text") .fontWeight(.bold) .italic() .underline() .underline(color: .blue) .strikethrough() .strikethrough(color: .red) .kerning(2) // Letter spacing .tracking(2) // Similar to kerning .baselineOffset(10) // Vertical offset .textCase(.uppercase) .textCase(.lowercase)

Text Truncation and Lines

Text("Long text that might need truncation...") .lineLimit(2) .lineLimit(1...3) // Range (iOS 16+) .truncationMode(.tail) // .head, .middle, .tail .allowsTightening(true) // Reduce spacing before truncating .minimumScaleFactor(0.5) // Scale down to fit

Text Alignment

Text("Aligned text") .multilineTextAlignment(.leading) .multilineTextAlignment(.center) .multilineTextAlignment(.trailing)

// Frame alignment for single line Text("Single") .frame(maxWidth: .infinity, alignment: .leading)

Native Markdown Support

Automatic Markdown Rendering

SwiftUI Text views automatically render Markdown:

// Basic Markdown in Text Text("Bold, italic, and strikethrough") Text("Visit Apple") Text("inline code looks different")

// Combined formatting Text("This is bold and italic together")

Supported Markdown Syntax

// Emphasis Text("italic or italic") Text("bold or bold") Text("bold italic")

// Strikethrough Text("deleted")

// Code Text("monospace")

// Links Text("Link Text")

// Soft breaks Text("Line one\nLine two")

Markdown from Variables

// String interpolation with AttributedString let markdownString = "Important: Check the documentation"

// Option 1: Direct (for literals only) Text("Bold text")

// Option 2: AttributedString for variables if let attributed = try? AttributedString(markdown: markdownString) { Text(attributed) }

AttributedString

Creating AttributedString

// From plain string var attributed = AttributedString("Hello World")

// From Markdown let markdown = try? AttributedString(markdown: "Bold and italic")

// From localized string let localized = AttributedString(localized: "greeting_message")

Applying Attributes

var text = AttributedString("Hello World")

// Whole string attributes text.font = .title text.foregroundColor = .blue text.backgroundColor = .yellow

// Range-based attributes if let range = text.range(of: "World") { text[range].font = .title.bold() text[range].foregroundColor = .red }

Available Attributes

var text = AttributedString("Styled")

// Typography text.font = .body text.foregroundColor = .primary text.backgroundColor = .clear

// Text decoration text.strikethroughStyle = .single text.strikethroughColor = .red text.underlineStyle = .single text.underlineColor = .blue

// Spacing text.kern = 2.0 // Character spacing text.tracking = 1.0 // Similar to kern text.baselineOffset = 5 // Vertical offset

// Links text.link = URL(string: "https://apple.com")

// Accessibility text.accessibilityLabel = "Custom label" text.accessibilitySpeechSpellsOutCharacters = true

Combining AttributedStrings

var greeting = AttributedString("Hello ") greeting.font = .title

var name = AttributedString("World") name.font = .title.bold() name.foregroundColor = .blue

let combined = greeting + name Text(combined)

Iterating Over Runs

let attributed = try? AttributedString(markdown: "Bold and italic")

// Iterate through styled runs for run in attributed?.runs ?? [] { print("Text: (attributed?[run.range] ?? "")") print("Font: (run.font ?? .body)") }

Markdown Parsing Options

Basic Parsing

let source = "# Heading\nBold text"

// Default parsing let attributed = try? AttributedString(markdown: source)

// With options let options = AttributedString.MarkdownParsingOptions( interpretedSyntax: .inlineOnlyPreservingWhitespace ) let parsed = try? AttributedString(markdown: source, options: options)

Interpreted Syntax Options

// Full Markdown (default) .interpretedSyntax: .full

// Inline only (no block elements) .interpretedSyntax: .inlineOnly

// Inline, preserving whitespace .interpretedSyntax: .inlineOnlyPreservingWhitespace

Handling Parse Errors

do { let attributed = try AttributedString(markdown: source) // Use attributed string } catch { // Fallback to plain text let plain = AttributedString(source) }

Custom Attribute Scopes

// Define custom attributes enum MyAttributes: AttributeScope { let customHighlight: CustomHighlightAttribute }

struct CustomHighlightAttribute: CodableAttributedStringKey { typealias Value = Bool static let name = "customHighlight" }

// Extend AttributeScopes extension AttributeScopes { var myAttributes: MyAttributes.Type { MyAttributes.self } }

// Use custom attributes var text = AttributedString("Highlighted") text.customHighlight = true

Rich Text Editing (iOS 26)

TextEditor with AttributedString

iOS 26 introduces first-class rich text editing:

struct RichTextEditor: View { @State private var content = AttributedString("Edit me with formatting") @State private var selection = AttributedTextSelection()

var body: some View {
    TextEditor(text: $content, selection: $selection)
        .textEditorStyle(.plain)
}

}

AttributedTextSelection

struct FormattingEditor: View { @State private var content = AttributedString() @State private var selection = AttributedTextSelection()

var body: some View {
    VStack {
        // Formatting toolbar
        HStack {
            Button("Bold") { toggleBold() }
            Button("Italic") { toggleItalic() }
            Button("Underline") { toggleUnderline() }
        }

        TextEditor(text: $content, selection: $selection)
    }
}

func toggleBold() {
    content.transformAttributes(in: selection.range) { container in
        // Toggle bold
        if container.font?.isBold == true {
            container.font = container.font?.removingBold()
        } else {
            container.font = container.font?.bold()
        }
    }
}

func toggleItalic() {
    content.transformAttributes(in: selection.range) { container in
        if container.font?.isItalic == true {
            container.font = container.font?.removingItalic()
        } else {
            container.font = container.font?.italic()
        }
    }
}

func toggleUnderline() {
    content.transformAttributes(in: selection.range) { container in
        if container.underlineStyle != nil {
            container.underlineStyle = nil
        } else {
            container.underlineStyle = .single
        }
    }
}

}

Built-in Keyboard Shortcuts

iOS 26 TextEditor supports standard keyboard shortcuts:

  • ⌘B - Bold

  • ⌘I - Italic

  • ⌘U - Underline

Font Resolution Context

TextEditor(text: $content, selection: $selection) .environment(.fontResolutionContext, FontResolutionContext( defaultFont: .body, defaultForegroundColor: .primary ))

Text Interpolation

Format Styles

// Numbers Text("Count: (count)") Text("Price: (price, format: .currency(code: "USD"))") Text("Percentage: (value, format: .percent)") Text("Decimal: (number, format: .number.precision(.fractionLength(2)))")

// Dates Text("Date: (date, format: .dateTime)") Text("Day: (date, format: .dateTime.day().month().year())") Text("Time: (date, format: .dateTime.hour().minute())")

// Relative dates Text(date, style: .relative) // "2 hours ago" Text(date, style: .timer) // "2:30:00" Text(date, style: .date) // "June 15, 2025" Text(date, style: .time) // "3:30 PM" Text(date, style: .offset) // "+2 hours"

// Date ranges Text(startDate...endDate)

// Lists Text(names, format: .list(type: .and)) // "Alice, Bob, and Charlie"

// Measurements Text(distance, format: .measurement(width: .abbreviated))

Person Name Components

let name = PersonNameComponents(givenName: "John", familyName: "Doe") Text(name, format: .name(style: .long))

ByteCount

Text(fileSize, format: .byteCount(style: .file))

Localization

LocalizedStringKey

// Automatic localization lookup Text("welcome_message") // Looks up in Localizable.strings

// With interpolation Text("greeting_(username)") // "greeting_%@" in strings file

// Explicit localized string Text(LocalizedStringKey("settings_title"))

String Catalogs (.xcstrings)

Modern localization uses String Catalogs:

// In String Catalog (Localizable.xcstrings) // Key: "items_count" // English: "%lld items" // French: "%lld éléments"

Text("items_count (count)")

Pluralization

// In String Catalog, define variants: // "items_count" with plural variants: // - zero: "No items" // - one: "1 item" // - other: "%lld items"

Text("items_count (count)")

AttributedString Localization

// Localized with attributes let attributed = AttributedString(localized: "formatted_message") Text(attributed)

Text Selection

Enabling Selection

Text("Selectable text that users can copy") .textSelection(.enabled)

// Disable selection Text("Not selectable") .textSelection(.disabled)

Selection on Lists

List(items) { item in Text(item.content) .textSelection(.enabled) }

TextField and SecureField

Basic TextField

@State private var text = ""

TextField("Placeholder", text: $text)

// With prompt TextField("Username", text: $username, prompt: Text("Enter username"))

// Axis for multiline TextField("Description", text: $description, axis: .vertical) .lineLimit(3...6)

TextField Styles

TextField("Input", text: $text) .textFieldStyle(.automatic) .textFieldStyle(.plain) .textFieldStyle(.roundedBorder)

SecureField

SecureField("Password", text: $password)

Formatting TextField

// Number input TextField("Amount", value: $amount, format: .currency(code: "USD"))

// Date input TextField("Date", value: $date, format: .dateTime)

// Custom format TextField("Phone", value: $phone, format: PhoneNumberFormat())

TextField Focus

@FocusState private var isFocused: Bool

TextField("Input", text: $text) .focused($isFocused)

Button("Focus") { isFocused = true }

Keyboard Types

TextField("Email", text: $email) .keyboardType(.emailAddress) .textContentType(.emailAddress) .autocapitalization(.none) .autocorrectionDisabled()

TextField("Phone", text: $phone) .keyboardType(.phonePad) .textContentType(.telephoneNumber)

TextField("URL", text: $url) .keyboardType(.URL) .textContentType(.URL)

Submit Actions

TextField("Search", text: $query) .onSubmit { performSearch() } .submitLabel(.search)

// Submit labels: .done, .go, .join, .next, .return, .search, .send

Label

Basic Label

Label("Settings", systemImage: "gear") Label("Document", image: "doc-icon")

// Custom label Label { Text("Custom") .font(.headline) } icon: { Image(systemName: "star.fill") .foregroundStyle(.yellow) }

Label Styles

Label("Title", systemImage: "star") .labelStyle(.automatic) .labelStyle(.titleOnly) .labelStyle(.iconOnly) .labelStyle(.titleAndIcon)

Link

Basic Links

Link("Apple", destination: URL(string: "https://apple.com")!)

Link(destination: URL(string: "https://apple.com")!) { Label("Visit Apple", systemImage: "safari") }

Links in Text

// Using Markdown Text("Visit our website for more info")

// Using AttributedString var text = AttributedString("Visit our website") if let range = text.range(of: "our website") { text[range].link = URL(string: "https://example.com") text[range].foregroundColor = .blue } Text(text)

Privacy Sensitive Content

Redaction

Text(sensitiveData) .privacySensitive()

// Manual redaction Text("Hidden Content") .redacted(reason: .privacy) .redacted(reason: .placeholder)

// Unredacted Text("Always Visible") .unredacted()

Conditional Redaction

struct ContentView: View { @Environment(.redactionReasons) var redactionReasons

var body: some View {
    if redactionReasons.contains(.privacy) {
        Text("•••••")
    } else {
        Text(accountBalance, format: .currency(code: "USD"))
    }
}

}

Text Rendering Performance

Efficient Text Updates

// GOOD: Separate text views for changing content VStack { Text("Static label:") Text("(dynamicValue)") // Only this updates }

// AVOID: Combining static and dynamic in one Text Text("Static label: (dynamicValue)") // Whole text re-renders

Large Text Handling

// For very long text, use ScrollView ScrollView { Text(veryLongContent) .textSelection(.enabled) }

// Or LazyVStack for segmented content ScrollView { LazyVStack(alignment: .leading) { ForEach(paragraphs, id: .self) { paragraph in Text(paragraph) .padding(.bottom) } } }

Accessibility

VoiceOver Customization

Text("5 stars") .accessibilityLabel("5 out of 5 stars")

Text("$99") .accessibilityLabel("99 dollars")

// Heading level Text("Section Title") .accessibilityAddTraits(.isHeader)

Dynamic Type Support

// Respect user's text size preference Text("Accessible text") .font(.body) // Scales with Dynamic Type

// Fixed size (use sparingly) Text("Fixed size") .font(.system(size: 14)) .dynamicTypeSize(.large) // Cap at large

// Size range Text("Limited scaling") .dynamicTypeSize(.small...(.accessibilityLarge))

Best Practices

  1. Use Semantic Fonts

// GOOD: Semantic fonts scale with Dynamic Type .font(.headline) .font(.body) .font(.caption)

// AVOID: Fixed sizes unless necessary .font(.system(size: 16))

  1. Support Markdown for User Content

// Parse user input as Markdown safely func renderUserContent(_ input: String) -> Text { if let attributed = try? AttributedString( markdown: input, options: .init(interpretedSyntax: .inlineOnlyPreservingWhitespace) ) { return Text(attributed) } return Text(input) }

  1. Enable Text Selection for Copyable Content

Text(address) .textSelection(.enabled)

  1. Handle Localization Properly

// Use LocalizedStringKey for user-facing text Text("button_title")

// Use verbatim for data Text(verbatim: userGeneratedContent)

  1. Consider Privacy

Text(sensitiveInfo) .privacySensitive()

Official Resources

  • Text Documentation

  • AttributedString Documentation

  • TextEditor Documentation

  • Markdown in SwiftUI

  • WWDC21: What's new in Foundation

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

photographer-testino

No summary provided by upstream source.

Repository SourceNeeds Review
General

photographer-lindbergh

No summary provided by upstream source.

Repository SourceNeeds Review
General

photographer-lachapelle

No summary provided by upstream source.

Repository SourceNeeds Review
General

photographer-vonunwerth

No summary provided by upstream source.

Repository SourceNeeds Review