A high-performance markdown rendering library for iOS, macOS, and visionOS.
demo.mp4
- Full GFM (GitHub Flavored Markdown) support: headings, lists, tables, blockquotes, task lists, and more
- Native syntax highlighting powered by tree-sitter — no JavaScript runtime overhead
- 19 languages: Swift, C, C++, C#, Python, JavaScript, TypeScript, TSX, Go, Rust, Java, Kotlin, Ruby, Bash, SQL, YAML, JSON, HTML, CSS
- LaTeX math rendering
- Inline image rendering with async loading and caching
- Comprehensive theming with fonts, colors, and spacing
- Text selection with long-press, double-tap, and triple-tap gestures
- VoiceOver accessibility for text, code blocks, tables, and math content
- UIKit and AppKit support via a single API
Syntax highlighting uses tree-sitter's native C parser instead of JavaScript-based solutions like highlight.js. This eliminates the JavaScriptCore runtime entirely and produces color ranges directly from semantic parse trees. Language parsers are initialized lazily — only the languages actually used are loaded.
| Benchmark | Time |
|---|---|
| Plain-text stream append (steady-state) | <0.1 ms |
| Highlight 50 lines | ~2 ms |
| Highlight 500 lines | ~21 ms |
| Parse 500 blocks | ~5 ms |
| Parse + preprocess 300 blocks | ~3 ms |
The plain-text streaming fast path applies to safe token appends that do not introduce new markdown syntax, allowing the view to skip reparsing and update only the trailing paragraph.
- iOS 16+ / macOS 13+ / visionOS 1+
- Swift 5.9+
Add to your Package.swift:
dependencies: [
.package(url: "https://github.com/user/MarkdownView", from: "1.0.0"),
]Then add "MarkdownView" as a dependency of your target.
Show UIKit example
import UIKit
import MarkdownParser
import MarkdownView
class ViewController: UIViewController {
private let markdownView = MarkdownTextView()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(markdownView)
markdownView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
markdownView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
markdownView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
markdownView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
])
// Parse and render markdown
let parser = MarkdownParser()
let result = parser.parse("""
# Hello, Markdown!
This is **bold**, *italic*, and `inline code`.
```python
def greet(name):
return f"Hello, {name}!"
```
- Item one
- Item two
- Item three
""")
let content = MarkdownTextView.PreprocessedContent(
parserResult: result,
theme: .default
)
markdownView.setMarkdown(content)
// Handle link taps
markdownView.linkHandler = { payload, range, point in
switch payload {
case .url(let url):
UIApplication.shared.open(url)
case .string(let string):
print("Tapped link: \(string)")
}
}
// Handle image taps
markdownView.imageTapHandler = { source, point in
print("Image tapped: \(source)")
}
}
}Wrap MarkdownTextView in UIViewRepresentable when you want to use it from SwiftUI on iOS or visionOS.
Show SwiftUI example
import SwiftUI
import MarkdownParser
import MarkdownView
struct MarkdownTextViewRepresentable: UIViewRepresentable {
let markdown: String
var theme: MarkdownTheme = .default
func makeUIView(context: Context) -> MarkdownTextView {
let view = MarkdownTextView()
view.backgroundColor = .clear
view.isOpaque = false
return view
}
func updateUIView(_ uiView: MarkdownTextView, context: Context) {
uiView.theme = theme
let parser = MarkdownParser()
let result = parser.parse(markdown)
let content = MarkdownTextView.PreprocessedContent(
parserResult: result,
theme: theme
)
uiView.setMarkdown(content)
}
func sizeThatFits(
_ proposal: ProposedViewSize,
uiView: MarkdownTextView,
context: Context
) -> CGSize? {
guard let width = proposal.width, width > 0 else {
return uiView.intrinsicContentSize
}
let measuredSize = uiView.boundingSize(for: width)
return CGSize(width: width, height: measuredSize.height)
}
}
struct ContentView: View {
private let markdown = """
# Hello, SwiftUI!
This `MarkdownTextView` is wrapped with `UIViewRepresentable`.
```swift
struct GreetingView: View {
var body: some View {
Text("Hello from MarkdownView")
}
}
```
"""
var body: some View {
ScrollView {
MarkdownTextViewRepresentable(markdown: markdown)
.padding(.horizontal)
}
}
}Show theming example
var theme = MarkdownTheme()
// Customize fonts
theme.fonts.body = .preferredFont(forTextStyle: .body)
theme.fonts.code = .monospacedSystemFont(ofSize: 14, weight: .regular)
// Customize colors
theme.colors.body = .label
theme.colors.code = .secondaryLabel
theme.colors.codeBackground = .secondarySystemBackground
// Scale all fonts
theme.scaleFont(by: .large)
markdownView.theme = themeThe library is split into two modules:
- MarkdownParser — Converts markdown strings into an AST using swift-cmark (GFM extensions included). No UI dependencies.
- MarkdownView — Renders the AST into native views with syntax highlighting, math rendering, and interactive links.
MIT
Inspired by Lakr233/MarkdownView