A SwiftUI rich text editor backed by UITextView, with a formatting toolbar above the keyboard.
- Bold, italic, underline, strikethrough and inline code, which combine freely and keep the font family
- Headings, bulleted and numbered lists, quotes and code blocks, with Return and Delete behaving as in Notes
- Links, font size, text color and per-paragraph alignment
- Images inserted at the caret through the system photo picker (no permission prompt)
- Markdown shortcuts while typing:
#,-,1.,>,**bold**,*italic*,_italic_,~~strike~~and backticks - Markdown, HTML and lossless archive import and export
- Undo and redo for every edit, including formatting; ⌘B, ⌘I and ⌘U on hardware keyboards
- A configurable toolbar whose state follows the selection, with dark mode and VoiceOver support
import SwiftUI
import TextEditor
struct ContentView: View {
@State private var text = NSAttributedString()
var body: some View {
ScrollView {
RichTextEditor(text: $text, placeholder: "Write something…")
.padding()
}
}
}The editor grows with its content, so place it in a ScrollView for long documents.
Typing Markdown at the start of a paragraph or around a word turns it into formatting, and undo brings the Markdown back. Turn it off with a modifier:
RichTextEditor(text: $text)
.markdownShortcuts(false)Pick sections in display order, or pass an empty array to hide the toolbar:
RichTextEditor(text: $text, toolbar: [.textStyle, .blockStyle, .lists, .link, .history])For custom colors or your own buttons, pass a configuration:
RichTextEditor(text: $text, toolbar: RichTextToolbarConfiguration(
sections: [.textStyle, .lists, .colorPalette],
colors: [RichTextColor("Ink", .label), RichTextColor("Brand", .systemIndigo)],
customActions: [
RichTextToolbarAction(title: "Quote", systemImage: "text.quote",
isActive: { $0.blockStyle == .quote },
action: { $0.toggleBlockStyle(.quote) }),
]
))To place the toolbar somewhere other than above the keyboard, pass toolbar: nil and add RichTextToolbar(context:configuration:) yourself.
let markdown = RichTextSerializer.markdown(from: text)
let html = RichTextSerializer.html(from: text)
let data = try RichTextSerializer.archivedData(from: text)
text = RichTextSerializer.attributedString(markdown: markdown)
text = RichTextSerializer.attributedString(html: html)
text = try RichTextSerializer.attributedString(archivedData: data)| Format | Keeps | Drops |
|---|---|---|
| Archive | everything | nothing — use it for drafts |
| HTML | blocks, inline styles, links, colors, alignment, images | font sizes |
| Markdown | blocks, bold, italic, strikethrough, code, links, images | underline, colors, sizes, alignment |
Images are embedded as base64 data: URIs in HTML and Markdown. On import, only data: URI images are loaded; remote images are never fetched, so Markdown keeps them as a link and HTML drops them.
RichTextContext publishes the formatting at the current selection and exposes the same commands as the toolbar.
@StateObject private var context = RichTextContext()
var body: some View {
VStack {
RichTextEditor(text: $text, context: context, toolbar: [])
Button("Bold") { context.toggle(.bold) }
.foregroundColor(context.isActive(.bold) ? .accentColor : .primary)
Button("Heading") { context.toggleBlockStyle(.heading1) }
}
}Example/TextEditorExample.xcodeproj is a small notes app showing the editor, Markdown and HTML export, and draft saving. Open it and run; it uses this package from the parent folder. The project is generated from Example/project.yml with XcodeGen.
The package includes a DocC catalog with getting-started, toolbar and serialization guides. In Xcode, choose Product › Build Documentation.
Requires iOS 16 and Xcode 16 or later.
Add the package in Xcode via File › Add Package Dependencies…, or in Package.swift:
.package(url: "https://github.com/nothingsh/TextEditor", from: "2.0.0")- The minimum platform is now iOS 16.
RichTextEditor(richText:onCommit:)is replaced byRichTextEditor(text:)with aBinding<NSAttributedString>, which updates on every edit. Use.onChange(of:)to save.accessory:is renamedtoolbar:.EditorSectiongains.blockStyle,.lists,.linkand.history, and the default toolbar (EditorSection.allCases) shows them all; pass the sections you want to keep the 1.x toolbar.- Building now requires Xcode 16 or later.
- The public
Color(hex:)andUIColor(hex:)extensions have been removed.
- Headings, lists, quotes and code
- Links
- HTML and Markdown import and export
- Configurable toolbar items and colors
- Markdown shortcuts while typing
- Checklists and nested lists
- Font selection
- macOS support

