text-editing

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

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.
在SwiftUI中显示样式文本和构建富文本编辑器的方案。涵盖Text样式设置、AttributedString、带选中文本格式化功能的TextEditor以及自定义格式定义。

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
当用户有以下需求时使用本技能:
  • 需要显示带样式或格式的文本
  • 需要带有粗体/斜体/下划线控件的富文本编辑功能
  • 询问AttributedString或TextEditor相关内容
  • 需要文本格式工具栏
  • 询问Text视图中的Markdown渲染
  • 需要自定义文本属性或格式约束
  • 需要基于选中文本的格式化功能

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
你需要什么文本功能?
|
+- 显示只读样式文本
|  +- 简单样式(单一风格)-> 下方的文本样式设置章节
|  +- 单个字符串包含混合样式 -> 下方的AttributedString章节
|  +- Markdown内容 -> 下方的Markdown章节
|
+- 编辑纯文本
|  +- 使用TextEditor(text: $stringBinding)
|
+- 编辑富/样式文本
|  +- 基础富文本编辑器 -> 下方的富文本编辑章节
|  +- 带格式工具栏 -> 下方的格式控件章节
|  +- 带自定义约束 -> 下方的自定义格式章节

API Availability

API兼容性

APIMinimum VersionNotes
Text
with modifiers
iOS 13.font(), .bold(), .italic()
TextEditor(text:)
iOS 14Plain string binding
.foregroundStyle()
iOS 15Preferred over .foregroundColor()
AttributedString
iOS 15Rich text model
.bold(condition)
iOS 16Conditional bold/italic
.underline(pattern:)
iOS 16Dash, dot patterns
TextEditor(text:selection:)
iOS 18AttributedString + selection
AttributedTextSelection
iOS 18Selection-based formatting
text.transformAttributes(in:)
iOS 18Modify attributes in selection
AttributedTextFormattingDefinition
iOS 18Custom formatting constraints
@Environment(\.fontResolutionContext)
iOS 18Resolve font properties
API最低版本说明
Text
搭配修饰符
iOS 13.font(), .bold(), .italic()
TextEditor(text:)
iOS 14纯字符串绑定
.foregroundStyle()
iOS 15优先于.foregroundColor()使用
AttributedString
iOS 15富文本模型
.bold(condition)
iOS 16条件式粗体/斜体
.underline(pattern:)
iOS 16虚线、点线样式
TextEditor(text:selection:)
iOS 18AttributedString + 选中文本
AttributedTextSelection
iOS 18基于选中文本的格式化
text.transformAttributes(in:)
iOS 18修改选中文本的属性
AttributedTextFormattingDefinition
iOS 18自定义格式约束
@Environment(\.fontResolutionContext)
iOS 18解析字体属性

Top 5 Mistakes

五大常见错误

#MistakeFix
1Using
.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
3Expecting full Markdown in
Text
Text only supports inline Markdown: bold, italic, [links]. No lists, tables, code blocks, images
4Creating new AttributedString instances on every body evaluationCache complex AttributedString objects in @State or computed properties outside body
5Using
TextEditor(text: $string)
for rich text
Use
TextEditor(text: $attributedString, selection: $selection)
with AttributedString binding (iOS 18+)
序号错误修复方案
1在新项目中使用
.foregroundColor()
使用
.foregroundStyle()
— 支持渐变和ShapeStyle
2文本使用
.animation(.spring())
但未传入
value:
参数
iOS 15中已弃用 — 务必传入
value:
参数
3期望
Text
支持完整Markdown语法
Text仅支持行内Markdown:粗体斜体、[链接]。不支持列表、表格、代码块、图片
4在每次body求值时创建新的AttributedString实例将复杂的AttributedString对象缓存到@State或body外的计算属性中
5使用
TextEditor(text: $string)
实现富文本
使用
TextEditor(text: $attributedString, selection: $selection)
搭配AttributedString绑定(iOS 18+)

Text Styling Quick Reference

文本样式速查

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
// 字体
Text("Hello").font(.headline)
Text("Custom").font(.system(size: 24, design: .rounded))

// 字重
Text("Bold").fontWeight(.bold)

// 颜色 — 优先使用foregroundStyle而非foregroundColor
Text("Styled").foregroundStyle(.red)
Text("Gradient").foregroundStyle(
    .linearGradient(colors: [.yellow, .blue], startPoint: .top, endPoint: .bottom)
)

// 装饰效果
Text("Bold").bold()
Text("Conditional").bold(someCondition)
Text("Underline").underline(true, pattern: .dash, color: .blue)
Text("Strike").strikethrough(true, pattern: .dot, color: .red)

// 布局
Text("Centered").multilineTextAlignment(.center)
Text("Spaced").lineSpacing(10)
Text("Truncated").lineLimit(2).truncationMode(.tail)

AttributedString

AttributedString

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)
swift
// 创建并设置文本范围样式
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
}

// 显示文本
Text(text)

Available Attributes

可用属性

AttributeTypeExample
.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)
属性类型示例
.font
Font
.headline
.foregroundColor
Color
.red
.backgroundColor
Color
.yellow
.underlineStyle
UnderlineStyle
.single
.underlineColor
Color
.blue
.strikethroughStyle
UnderlineStyle
.single
.strikethroughColor
Color
.red
.inlinePresentationIntent
InlinePresentationIntent
.stronglyEmphasized
(粗体),
.emphasized
(斜体)

Rich Text Editing (iOS 18+)

富文本编辑(iOS 18+)

swift
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
            }
        }
    }
}
swift
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

读取选中文本属性

swift
// 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
}
swift
// 获取选中文本/光标位置的当前属性
let attributes = selection.typingAttributes(in: text)
let currentColor = attributes.foregroundColor ?? .primary

// 为选中文本设置颜色
text.transformAttributes(in: &selection) {
    $0.foregroundColor = .blue
}

Custom Formatting Definition (iOS 18+)

自定义格式定义(iOS 18+)

Constrain which formatting options are available in the editor:
swift
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)
限制编辑器中可用的格式化选项:
swift
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)
        }
    )
}

// 应用到TextEditor
TextEditor(text: $text, selection: $selection)
    .textFormattingDefinition(MyTextFormatting.self)

Markdown in Text

Text中的Markdown支持

swift
// Inline markdown support
Text("This is **bold** and *italic* text")
Text("Visit [Apple](https://www.apple.com)")
Text("Code: `inline code`")
swift
// 支持行内Markdown
Text("This is **bold** and *italic* text")
Text("Visit [Apple](https://www.apple.com)")
Text("Code: `inline code`")

Markdown Limitations

Markdown限制

Text supports only inline Markdown:
  • ✅ Bold (
    **text**
    ), italic (
    *text*
    ), links, inline code
  • ❌ Line breaks, block formatting, lists, tables, code blocks, images, block quotes
Text仅支持行内Markdown:
  • ✅ 粗体 (
    **text**
    )、斜体 (
    *text*
    )、链接、行内代码
  • ❌ 换行、块级格式、列表、表格、代码块、图片、块引用

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
  • 使用
    .foregroundStyle()
    替代已弃用的
    .foregroundColor()
  • 为截断文本搭配
    .lineLimit()
    .help()
    提示框或
    .textSelection(.enabled)
  • 支持动态类型 — 使用系统字体样式,而非硬编码字号
  • 颜色适配深色模式 — 使用系统颜色或语义化样式

AttributedString

AttributedString

  • Complex AttributedString cached in @State, not recreated in body
  • Range lookups use safe
    if let range = text.range(of:)
    pattern
  • Appropriate attributes used (
    .inlinePresentationIntent
    for semantic bold/italic)
  • 复杂的AttributedString缓存到@State中,不在body中重新创建
  • 使用安全的
    if let range = text.range(of:)
    模式查找文本范围
  • 使用合适的属性(语义化粗体/斜体使用
    .inlinePresentationIntent

TextEditor

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
  • 富文本使用
    TextEditor(text: $attributedString, selection: $selection)
    (iOS 18+)
  • 格式工具栏使用
    text.transformAttributes(in: &selection)
    模式
  • 字体解析使用
    @Environment(\.fontResolutionContext)
    实现切换逻辑
  • 为格式按钮提供无障碍标签

Localization

本地化

  • User-facing strings use
    LocalizedStringKey
  • Markdown in localized strings uses
    Text(LocalizedStringKey("**Bold** text"))
  • 用户可见字符串使用
    LocalizedStringKey
  • 本地化字符串中的Markdown使用
    Text(LocalizedStringKey("**Bold** text"))

References

参考资料