Module overview
The library consists of two Swift Package Manager modules:MarkdownParser
Converts markdown strings into an Abstract Syntax Tree (AST) using swift-cmark. Has no UI dependencies.
MarkdownView
Renders the AST into native views with syntax highlighting, math rendering, and interactive links.
MarkdownParser module
TheMarkdownParser module is a pure Swift package that transforms markdown text into a structured AST representation. It has zero UI dependencies, making it suitable for server-side use, command-line tools, or any context where you need to parse markdown without rendering it.
Key responsibilities
- Parse markdown using swift-cmark with GFM extensions
- Preprocess LaTeX math expressions (
$...$,$$...$$,\\(...\\),\\[...\\]) - Generate typed AST nodes for blocks and inline elements
- Support incremental parsing for performance optimization
Core types
The parser defines two main node types:Sources/MarkdownParser/MarkdownBlockNode/MarkdownBlockNode.swift
Sources/MarkdownParser/MarkdownInlineNode/MarkdownInlineNode.swift
Both node types conform to
Hashable, Equatable, and Codable, making them easy to compare, store, and transmit.Usage example
MarkdownView module
TheMarkdownView module takes the AST from MarkdownParser and renders it into native UIKit/AppKit views. It handles all visual presentation, interaction, and platform-specific rendering.
Key responsibilities
- Render AST nodes into
NSAttributedStringwith rich formatting - Syntax highlight code blocks using tree-sitter parsers
- Render LaTeX math expressions as inline images
- Load and display inline images asynchronously
- Handle text selection, link taps, and accessibility
- Manage view lifecycle and layout
Core components
MarkdownTextView
The main view class that displays rendered markdown. Available on both iOS/visionOS (via UIKit) and macOS (via AppKit).Sources/MarkdownView/MarkdownTextView/MarkdownTextView.swift
LTXLabel
A custom text rendering view from the embeddedLitext module that provides:
- High-performance Core Text layout
- Text selection with gesture support
- Inline attachments for images and math
- Custom drawing callbacks for complex rendering
- VoiceOver accessibility
Sources/Litext/LTXLabel/LTXLabel.swift:11 for the full implementation.
TextBuilder
Converts AST nodes intoNSAttributedString with embedded views:
Sources/MarkdownView/MarkdownTextBuilder/TextBuilder.swift
The
TextBuilder supports incremental builds by reusing cached NSAttributedString segments for unchanged blocks. This makes streaming updates efficient.Module independence
The two-module architecture provides several benefits:Parse without rendering
Test parsing independently
Customize rendering
Data flow
Here’s how data flows through the architecture:Preprocessing step details
Preprocessing step details
The
PreprocessedContent class (see Sources/MarkdownView/MarkdownTextBuilder/PreprocessedContent.swift:13) combines:- AST blocks from the parser
- Syntax highlighting maps from tree-sitter (thread-safe, can run on background queue)
- Rendered math images from LaTeX (requires main thread for UIKit trait access)
- Image sources for async loading
Platform support
Both modules support iOS 16+, macOS 13+, and visionOS 1+. TheMarkdownView module uses conditional compilation to provide a unified API across UIKit and AppKit: