Loading...
Loading...
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 skill4agent add rshankras/claude-code-apple-skills text-editingWhat 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 |
|---|---|---|
| iOS 13 | .font(), .bold(), .italic() |
| iOS 14 | Plain string binding |
| iOS 15 | Preferred over .foregroundColor() |
| iOS 15 | Rich text model |
| iOS 16 | Conditional bold/italic |
| iOS 16 | Dash, dot patterns |
| iOS 18 | AttributedString + selection |
| iOS 18 | Selection-based formatting |
| iOS 18 | Modify attributes in selection |
| iOS 18 | Custom formatting constraints |
| iOS 18 | Resolve font properties |
| # | Mistake | Fix |
|---|---|---|
| 1 | Using | Use |
| 2 | | Deprecated in iOS 15 — always pass |
| 3 | Expecting full Markdown in | 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 | Use |
// 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 | |
| Color | |
| Color | |
| UnderlineStyle | |
| Color | |
| UnderlineStyle | |
| Color | |
| InlinePresentationIntent | |
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
}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***text*.foregroundStyle().foregroundColor().lineLimit().help().textSelection(.enabled)if let range = text.range(of:).inlinePresentationIntentTextEditor(text: $attributedString, selection: $selection)text.transformAttributes(in: &selection)@Environment(\.fontResolutionContext)LocalizedStringKeyText(LocalizedStringKey("**Bold** text"))